From 2fce91b8c0faf1290d8a35ee022dab3cdbc28a54 Mon Sep 17 00:00:00 2001
From: cloudroam <cloudroam>
Date: 星期五, 14 二月 2025 14:31:06 +0800
Subject: [PATCH] init

---
 app/src/main/res/menu/bottom_nav_menu.xml                                         |   19 
 app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt          |   42 
 gradle/libs.versions.toml                                                         |   32 
 app/src/main/res/drawable/ic_home_black_24dp.xml                                  |    9 
 app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt         |   13 
 .idea/misc.xml                                                                    |    9 
 app/.idea/misc.xml                                                                |   10 
 .idea/migrations.xml                                                              |   10 
 app/src/main/java/com/example/firstapp/ui/notifications/NotificationsViewModel.kt |   13 
 app/src/main/java/com/example/firstapp/database/AppDatabase.kt                    |  104 
 app/src/main/java/com/example/firstapp/utils/AppUtils.kt                          |  104 
 app/src/main/java/com/example/firstapp/utils/RandomUtils.kt                       |  293 ++
 .idea/gradle.xml                                                                  |   20 
 app/src/main/res/layout/activity_main.xml                                         |   33 
 app/src/main/java/com/example/firstapp/entity/SmsInfo.kt                          |   37 
 app/src/main/java/com/example/firstapp/utils/RSACrypt.kt                          |  209 +
 app/.idea/.gitignore                                                              |    3 
 app/src/main/res/values/strings.xml                                               |   10 
 app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt                    |   66 
 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml                                |    6 
 app/src/main/java/com/example/firstapp/utils/HistoryUtils.kt                      |  115 
 app/src/main/res/mipmap-xhdpi/ic_launcher.webp                                    |    0 
 .idea/kotlinc.xml                                                                 |    6 
 app/src/main/res/layout/fragment_notifications.xml                                |   22 
 app/src/main/res/layout/sms_code.xml                                              |   12 
 app/src/main/java/com/example/firstapp/utils/FrpcUtils.kt                         |   51 
 .idea/compiler.xml                                                                |    6 
 app/src/main/res/mipmap-mdpi/ic_launcher.webp                                     |    0 
 app/proguard-rules.pro                                                            |   21 
 app/src/main/res/xml/backup_rules.xml                                             |   13 
 app/src/main/res/layout/item_layout.xml                                           |   22 
 app/src/main/res/values/themes.xml                                                |   16 
 app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt                    |   44 
 app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt                     |   57 
 app/src/main/java/com/example/firstapp/utils/mail/PgpUtils.kt                     |  247 ++
 app/src/main/java/com/example/firstapp/utils/SM4Crypt.kt                          |   62 
 gradle/wrapper/gradle-wrapper.properties                                          |    8 
 app/src/main/java/com/example/firstapp/utils/tinker/TinkerLoadLibrary.kt          |  196 +
 app/src/main/res/values/colors.xml                                                |   10 
 app/src/main/res/values-night/themes.xml                                          |   16 
 app/src/main/res/drawable/ic_dashboard_black_24dp.xml                             |    9 
 app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt  |   42 
 app/src/main/java/com/example/firstapp/database/repository/MsgRepository.kt       |   21 
 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp                                   |    0 
 app/src/main/res/navigation/mobile_navigation.xml                                 |   25 
 app/src/main/res/drawable/ic_launcher_foreground.xml                              |   30 
 versions.gradle                                                                   |  223 +
 app/src/main/java/com/example/firstapp/database/entity/Code.kt                    |   18 
 app/src/main/java/com/example/firstapp/utils/DrawableUtils.kt                     |   59 
 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp                            |    0 
 app/src/main/res/drawable/ic_sim2.xml                                             |   13 
 app/src/main/res/layout/fragment_home.xml                                         |   30 
 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml                          |    6 
 app/src/main/res/drawable/ic_sms.xml                                              |    9 
 gradle.properties                                                                 |   39 
 app/src/main/java/com/example/firstapp/entity/Item.kt                             |    7 
 app/src/androidTest/java/com/example/firstapp/ExampleInstrumentedTest.kt          |   24 
 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp                               |    0 
 app/src/main/java/com/example/firstapp/utils/tinker/ShareReflectUtil.kt           |  240 +
 app/src/main/java/com/example/firstapp/utils/mail/EmailSender.kt                  |  174 +
 app/src/main/java/com/example/firstapp/receiver/CactusReceiver.kt                 |   33 
 app/libs/frpclib-sources.jar                                                      |    0 
 .idea/deploymentTargetSelector.xml                                                |   18 
 app/src/main/java/com/example/firstapp/entity/Rule.kt                             |   17 
 app/src/main/res/drawable/ic_sim.xml                                              |   19 
 app/src/main/java/com/example/firstapp/core/Core.kt                               |   29 
 app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt                   |   33 
 app/src/main/java/com/example/firstapp/service/HttpServerService.kt               |   62 
 app/src/main/java/com/example/firstapp/database/AppDatabase_bak.kt                |  442 +++
 gradlew.bat                                                                       |   89 
 app/libs/frpclib.aar                                                              |    0 
 app/src/main/java/com/example/firstapp/utils/Base64.kt                            |   88 
 app/build.gradle                                                                  |  266 ++
 app/src/main/java/com/example/firstapp/utils/BluetoothUtils.kt                    |   38 
 app/src/main/res/values/dimens.xml                                                |    5 
 gradlew                                                                           |  185 +
 app/src/main/java/com/example/firstapp/utils/Constants.kt                         |  318 ++
 app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt      |   21 
 app/.idea/gradle.xml                                                              |   12 
 .idea/.gitignore                                                                  |    3 
 app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt                      |   32 
 app/src/main/java/com/example/firstapp/utils/CactusSave.kt                        |   15 
 app/src/main/res/drawable/ic_notifications_black_24dp.xml                         |    9 
 app/src/main/res/mipmap-hdpi/ic_launcher.webp                                     |    0 
 app/x-library.gradle                                                              |   66 
 app/src/main/java/com/example/firstapp/utils/Log.kt                               |  161 +
 .idea/.name                                                                       |    1 
 .idea/codeStyles/codeStyleConfig.xml                                              |    5 
 app/src/main/java/com/example/firstapp/App.kt                                     |  455 +++
 app/src/main/java/com/example/firstapp/utils/CacheUtils.kt                        |  108 
 .gitignore                                                                        |   27 
 app/src/main/res/xml/data_extraction_rules.xml                                    |   19 
 gradle/wrapper/gradle-wrapper.jar                                                 |    0 
 app/src/main/res/drawable/ic_launcher_background.xml                              |  170 +
 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp                               |    0 
 app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt                   |   36 
 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp                              |    0 
 app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt                       |   42 
 app/src/main/java/com/example/firstapp/utils/LocationUtils.kt                     |   72 
 app/src/main/res/layout/fragment_dashboard.xml                                    |   22 
 build.gradle                                                                      |   45 
 app/src/main/java/com/example/firstapp/MainActivity.kt                            |  131 +
 app/src/main/res/drawable/ic_sim1.xml                                             |   13 
 app/src/main/java/com/example/firstapp/utils/mail/SmimeUtils.kt                   |  252 ++
 app/src/main/java/com/example/firstapp/database/entity/Msg.kt                     |   45 
 app/.gitignore                                                                    |    1 
 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp                             |    0 
 settings.gradle                                                                   |   39 
 app/src/main/AndroidManifest.xml                                                  |  143 +
 app/src/main/java/com/example/firstapp/utils/SettingUtils.kt                      |  166 +
 app/src/test/java/com/example/firstapp/ExampleUnitTest.kt                         |   17 
 .idea/codeStyles/Project.xml                                                      |  123 +
 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp                                  |    0 
 app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt                    |   71 
 app/src/main/java/com/example/firstapp/utils/SharedPreference.kt                  |  131 +
 app/src/main/java/com/example/firstapp/service/BluetoothScanService.kt            |   73 
 app/src/main/java/com/example/firstapp/database/ext/ConvertersDate.kt             |   16 
 .idea/runConfigurations.xml                                                       |   17 
 app/src/main/res/drawable/ic_app.xml                                              |   23 
 app/src/main/java/com/example/firstapp/utils/KeepAliveUtils.kt                    |   52 
 120 files changed, 7,199 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore
index 32858aa..aa724b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,15 @@
-*.class
-
-# Mobile Tools for Java (J2ME)
-.mtj.tmp/
-
-# Package Files #
-*.jar
-*.war
-*.ear
-
-# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
-hs_err_pid*
+*.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
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..085690a
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+FirstApp
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..7643783
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,123 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JetCodeStyleSettings>
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <codeStyleSettings language="XML">
+      <option name="FORCE_REARRANGE_MODE" value="1" />
+      <indentOptions>
+        <option name="CONTINUATION_INDENT_SIZE" value="4" />
+      </indentOptions>
+      <arrangement>
+        <rules>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:android</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>xmlns:.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:id</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*:name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>name</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>style</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>^$</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>ANDROID_ATTRIBUTE_ORDER</order>
+            </rule>
+          </section>
+          <section>
+            <rule>
+              <match>
+                <AND>
+                  <NAME>.*</NAME>
+                  <XML_ATTRIBUTE />
+                  <XML_NAMESPACE>.*</XML_NAMESPACE>
+                </AND>
+              </match>
+              <order>BY_NAME</order>
+            </rule>
+          </section>
+        </rules>
+      </arrangement>
+    </codeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </codeStyleSettings>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel target="17" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..51a703e
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="deploymentTargetSelector">
+    <selectionStates>
+      <SelectionState runConfigName="app">
+        <option name="selectionMode" value="DROPDOWN" />
+        <DropdownSelection timestamp="2025-02-14T05:55:28.808113500Z">
+          <Target type="DEFAULT_BOOT">
+            <handle>
+              <DeviceId pluginId="PhysicalDevice" identifier="serial=88Y5T19C14010323" />
+            </handle>
+          </Target>
+        </DropdownSelection>
+        <DialogSelection />
+      </SelectionState>
+    </selectionStates>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..ad84da2
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="CHOOSE_PER_TEST" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="gradleJvm" value="17" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
+        <option name="resolveExternalAnnotations" value="false" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..fdf8d99
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="KotlinJpsPluginSettings">
+    <option name="version" value="1.9.0" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectMigrations">
+    <option name="MigrateToGradleLocalJavaHome">
+      <set>
+        <option value="$PROJECT_DIR$" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d7916a5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RunConfigurationProducerService">
+    <option name="ignoredProducers">
+      <set>
+        <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
+        <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
+        <option value="com.intellij.execution.junit.PatternConfigurationProducer" />
+        <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
+        <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
+        <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
+        <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
+        <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
+      </set>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/.idea/.gitignore b/app/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/app/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/app/.idea/gradle.xml b/app/.idea/gradle.xml
new file mode 100644
index 0000000..b898c0a
--- /dev/null
+++ b/app/.idea/gradle.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="testRunner" value="GRADLE" />
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/app/.idea/misc.xml b/app/.idea/misc.xml
new file mode 100644
index 0000000..6ff4d26
--- /dev/null
+++ b/app/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="ProjectRootManager" version="2" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/build/classes" />
+  </component>
+  <component name="ProjectType">
+    <option name="id" value="Android" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..ccc635c
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,266 @@
+plugins {
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.jetbrains.kotlin.android)
+//    id 'com.android.application'
+    id 'kotlin-android'
+    id 'kotlin-kapt'
+    id 'kotlin-parcelize'
+//    id 'img-optimizer'
+//    id 'com.yanzhenjie.andserver'
+}
+
+android {
+    namespace 'com.example.firstapp'
+    compileSdk 34
+
+    defaultConfig {
+        applicationId "com.example.firstapp"
+        minSdk 24
+        targetSdk 34
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_17
+        targetCompatibility JavaVersion.VERSION_17
+    }
+    kotlinOptions {
+        jvmTarget = '17'
+    }
+    buildFeatures {
+        viewBinding true
+    }
+}
+
+//dependencies {
+//
+//    implementation libs.androidx.core.ktx
+//    implementation libs.androidx.appcompat
+//    implementation libs.material
+//    implementation libs.androidx.constraintlayout
+//    implementation libs.androidx.lifecycle.livedata.ktx
+//    implementation libs.androidx.lifecycle.viewmodel.ktx
+//    implementation libs.androidx.navigation.fragment.ktx
+//    implementation libs.androidx.navigation.ui.ktx
+//    testImplementation libs.junit
+//    androidTestImplementation libs.androidx.junit
+//    androidTestImplementation libs.androidx.espresso.core
+//
+//
+//    def work_version = '2.8.1'
+//    //noinspection GradleDependency
+//    api("androidx.work:work-multiprocess:$work_version")
+//    //noinspection GradleDependency
+//    api("androidx.work:work-runtime-ktx:$work_version")
+//
+//    //Android Room
+//    def room_version = '2.5.2'
+//    //noinspection GradleDependency
+//    implementation "androidx.room:room-ktx:$room_version"
+//    //noinspection GradleDependency
+//    implementation "androidx.room:room-runtime:$room_version"
+//    //noinspection GradleDependency
+//    implementation "androidx.room:room-paging:$room_version"
+//    //noinspection GradleDependency
+//    implementation "androidx.room:room-rxjava2:$room_version"
+//    //noinspection KaptUsageInsteadOfKsp
+//    kapt "androidx.room:room-compiler:$room_version"
+//
+//    //基础功能的工具类
+//    implementation("com.github.xuexiangjys.XUtil:xutil-core:2.0.0")
+//    //附加功能的工具类
+//    implementation("com.github.xuexiangjys.XUtil:xutil-sub:2.0.0")
+//
+//
+//    //Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
+//    //https://github.com/gyf-dev/Cactus
+//    implementation 'com.gyf.cactus:cactus:1.1.3-beta13'
+////    implementation 'com.gyf.cactus:cactus-support:1.1.3-beta13'
+//
+//}
+
+android {
+    packagingOptions {
+        //去除FrpcLib的so,用时下载并动态加载
+        if (excludeFrpclib.toBoolean()) {
+            exclude 'lib/armeabi-v7a/libgojni.so'
+            exclude 'lib/arm64-v8a/libgojni.so'
+            exclude 'lib/x86/libgojni.so'
+            exclude 'lib/x86_64/libgojni.so'
+        }
+        jniLibs {
+            excludes += ["kotlin/**"]
+        }
+        resources {
+            merge 'META-INF/mailcap'
+            pickFirst 'META-INF/LICENSE.md'
+            pickFirst 'META-INF/NOTICE.md'
+            excludes += ['META-INF/DEPENDENCIES.txt', 'META-INF/LICENSE.txt', 'META-INF/NOTICE.txt', 'META-INF/NOTICE', 'META-INF/LICENSE', 'META-INF/DEPENDENCIES', 'META-INF/notice.txt', 'META-INF/license.txt', 'META-INF/dependencies.txt', 'META-INF/LGPL2.1']
+            excludes += ["META-INF/*.kotlin_module", "META-INF/*.version", "kotlin/**", "DebugProbesKt.bin"]
+        }
+    }
+}
+
+dependencies {
+
+    implementation libs.androidx.core.ktx
+    implementation libs.androidx.appcompat
+    implementation libs.material
+    implementation libs.androidx.constraintlayout
+    implementation libs.androidx.lifecycle.livedata.ktx
+    implementation libs.androidx.lifecycle.viewmodel.ktx
+    implementation libs.androidx.navigation.fragment.ktx
+    implementation libs.androidx.navigation.ui.ktx
+    testImplementation libs.junit
+    androidTestImplementation libs.androidx.junit
+    androidTestImplementation libs.androidx.espresso.core
+
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+    //frpc
+    implementation files('libs/frpclib.aar')
+
+    //MQTT协议
+    implementation("org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5")
+
+    testImplementation deps.junit
+    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+    androidTestImplementation deps.espresso.core
+
+    //noinspection GradleDependency
+    implementation 'androidx.core:core-ktx:1.9.0'
+    //noinspection GradleDependency
+    implementation 'androidx.activity:activity-ktx:1.6.1'
+    //noinspection GradleDependency
+    implementation 'androidx.fragment:fragment-ktx:1.5.5'
+    implementation "androidx.cardview:cardview:1.0.0"
+    implementation 'androidx.appcompat:appcompat:1.6.1'
+    implementation 'androidx.preference:preference-ktx:1.2.1'
+
+    //分包
+    implementation deps.androidx.multidex
+
+    //vLayout:https://github.com/alibaba/vlayout
+    implementation 'com.alibaba.android:vlayout:1.3.0'
+    //下拉刷新
+    implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-header:1.1.4'
+    implementation 'com.github.xuexiangjys.SmartRefreshLayout:refresh-layout:1.1.4'
+    //WebView
+    implementation 'com.github.xuexiangjys.AgentWeb:agentweb-core:1.0.1'
+    implementation 'com.github.xuexiangjys.AgentWeb:agentweb-download:1.0.1'//选填
+    //屏幕适配AutoSize:https://github.com/JessYanCoding/AndroidAutoSize
+    implementation 'me.jessyan:autosize:1.2.1'
+    //友盟统计
+    implementation 'com.umeng.umsdk:common:9.6.8'
+    implementation 'com.umeng.umsdk:asms:1.8.2'
+
+    //预加载占位控件
+    implementation 'me.samlss:broccoli:1.0.0'
+
+    //RichText:https://github.com/zzhoujay/RichText
+    implementation 'com.zzhoujay.richtext:richtext:3.0.8'
+
+    //美团多渠道打包
+    //implementation 'com.meituan.android.walle:library:1.1.6'
+
+    def work_version = '2.8.1'
+    //noinspection GradleDependency
+    api("androidx.work:work-multiprocess:$work_version")
+    //noinspection GradleDependency
+    api("androidx.work:work-runtime-ktx:$work_version")
+
+    //Android Room
+    def room_version = '2.5.2'
+    //noinspection GradleDependency
+    implementation "androidx.room:room-ktx:$room_version"
+    //noinspection GradleDependency
+    implementation "androidx.room:room-runtime:$room_version"
+    //noinspection GradleDependency
+    implementation "androidx.room:room-paging:$room_version"
+    //noinspection GradleDependency
+    implementation "androidx.room:room-rxjava2:$room_version"
+    //noinspection KaptUsageInsteadOfKsp
+    kapt "androidx.room:room-compiler:$room_version"
+
+    //CodeView:https://github.com/AmrDeveloper/CodeView
+    implementation 'io.github.amrdeveloper:codeview:1.3.9'
+
+    //LiveEventBus:https://github.com/JeremyLiao/LiveEventBus
+    implementation 'io.github.jeremyliao:live-event-bus-x:1.8.0'
+
+    //MarkdownView:https://github.com/tiagohm/MarkdownView
+    implementation 'com.github.tiagohm.MarkdownView:library:0.19.0'
+    //implementation 'com.github.tiagohm.MarkdownView:emoji:0.19.0'
+
+    def retrofit2_version = '2.9.0'
+    //noinspection GradleDependency
+    implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
+    //noinspection GradleDependency
+    implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version"
+    //noinspection GradleDependency
+    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version"
+
+    def paging_version = "3.1.1"
+    //noinspection GradleDependency
+    implementation "androidx.paging:paging-runtime-ktx:$paging_version"
+    // alternatively - without Android dependencies for tests
+    //noinspection GradleDependency
+    testImplementation "androidx.paging:paging-common-ktx:$paging_version"
+
+    //权限请求框架:https://github.com/getActivity/XXPermissions
+    implementation 'com.github.getActivity:XXPermissions:18.62'
+    //语种切换框架:https://github.com/getActivity/MultiLanguages
+    implementation 'com.github.getActivity:MultiLanguages:b47f7be' //9.3
+
+    def mail_version = '1.6.7'
+    implementation "com.sun.mail:android-mail:$mail_version"
+    implementation "com.sun.mail:android-activation:$mail_version"
+
+    //国密算法SM4 的JAVA实现(基于BC实现)
+    def bouncycastle_version = '1.77'
+    //noinspection GradleDependency
+    api "org.bouncycastle:bcprov-jdk18on:$bouncycastle_version"
+    //邮件 S/MIME 加密和签名
+    //implementation "org.spongycastle:bcmail-jdk18on:$bouncycastle_version" //Android下报错
+    //noinspection GradleDependency
+    implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastle_version"
+    //implementation "org.bouncycastle:bctls-jdk18on:$bouncycastle_version"
+    //邮件 PGP 加密和签名
+    //implementation "org.bouncycastle:bcpg-jdk18on:$bouncycastle_version" //Thunderbird无法解密
+    //PGPainless: https://github.com/pgpainless/pgpainless
+    implementation 'org.pgpainless:pgpainless-core:1.6.7'
+
+    //Android Keep Alive(安卓保活),Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
+    //https://github.com/gyf-dev/Cactus
+    implementation 'com.gyf.cactus:cactus:1.1.3-beta13'
+
+    //HTTP服务器:https://github.com/yanzhenjie/AndServer
+    implementation 'cn.ppps.andserver:api:2.1.12'
+    kapt 'cn.ppps.andserver:processor:2.1.12'
+
+    //Location 是一个通过 Android 自带的 LocationManager 来实现的定位功能:https://github.com/jenly1314/Location
+    implementation 'com.github.pppscn:location:1.0.0'
+
+    //Partial implementation of Quartz Cron Java for Android: https://github.com/gatewayapps/crondroid
+    implementation 'gatewayapps.crondroid:crondroid:1.0.0'
+    //Java Parser For Cron Expressions: https://github.com/grahamar/cron-parser
+    implementation 'net.redhogs.cronparser:cron-parser-core:3.5'
+
+    //侧边栏菜单:https://github.com/yarolegovich/SlidingRootNav
+    implementation 'com.yarolegovich:sliding-root-nav:1.1.1'
+
+    //基础功能的工具类
+    implementation("com.github.xuexiangjys.XUtil:xutil-core:2.0.0")
+    //附加功能的工具类
+    implementation("com.github.xuexiangjys.XUtil:xutil-sub:2.0.0")
+
+
+}
\ No newline at end of file
diff --git a/app/libs/frpclib-sources.jar b/app/libs/frpclib-sources.jar
new file mode 100644
index 0000000..48fe9ba
--- /dev/null
+++ b/app/libs/frpclib-sources.jar
Binary files differ
diff --git a/app/libs/frpclib.aar b/app/libs/frpclib.aar
new file mode 100644
index 0000000..ea179a1
--- /dev/null
+++ b/app/libs/frpclib.aar
Binary files differ
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/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
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/example/firstapp/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/firstapp/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..22e6a12
--- /dev/null
+++ b/app/src/androidTest/java/com/example/firstapp/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.firstapp
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.example.firstapp", appContext.packageName)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..088e3d9
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,143 @@
+<?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-feature
+        android:name="android.hardware.telephony"
+        android:required="false" />
+
+    <!--    用于查询所有安装的应用程序包。这个权限在 Android 11 及以上版本可能会受到限制。-->
+    <uses-permission
+        android:name="android.permission.QUERY_ALL_PACKAGES"
+        tools:ignore="QueryAllPackagesPermission" />
+
+    <!--    允许访问设备的精确位置信息(GPS)。-->
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <!--    允许访问设备的粗略位置信息(基于 Wi-Fi 或移动网络)。-->
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <!--    允许访问后台位置数据,用于在应用不在前台时获取位置信息。-->
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <!--    允许更改 Wi-Fi 网络的状态。-->
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <!--    允许访问网络状态(如是否连接到 Wi-Fi 或移动数据)。-->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <!--    允许访问 Wi-Fi 的当前状态(是否连接到 Wi-Fi,Wi-Fi 的信号强度等)。-->
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <!--    允许更改网络连接状态。-->
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <!--    允许访问网络上的资源(用于访问互联网)。-->
+    <uses-permission android:name="android.permission.INTERNET" />
+    <!-- 授予应用程序访问系统开机事件的权限 -->
+    <!--    允许读取设备的电话状态,通常这个权限需要系统签名的应用才能使用。-->
+    <uses-permission
+        android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许应用在设备启动完成时接收广播。-->
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <!--    允许读取外部存储(如 SD 卡中的文件)。-->
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <!--    允许计划精确的闹钟(例如在指定时间触发某些任务)。-->
+    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
+    <!--    允许写入外部存储(如 SD 卡中的文件)。-->
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <!--    允许管理外部存储的文件,通常需要更高的权限。-->
+    <uses-permission
+        android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+        tools:ignore="ScopedStorage" />
+    <!--    允许接收短信(SMS)。-->
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
+    <!--    允许接收多媒体短信(MMS)。-->
+    <uses-permission android:name="android.permission.RECEIVE_MMS" />
+    <!--    允许接收 WAP 推送消息。-->
+    <uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
+    <!--    允许读取短信内容。-->
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <!--    允许发送短信-->
+    <uses-permission android:name="android.permission.SEND_SMS" />
+    <!--    允许直接拨打电话。-->
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <!--    允许读取设备的电话状态(例如当前通话状态,设备是否有电话线等)。-->
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <!--    允许读取设备的电话号码。-->
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
+    <!--    允许读取通话记录。-->
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+    <!--    允许读取联系人数据。-->
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <!--    允许写入联系人数据。-->
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+    <!--    允许访问帐户信息。-->
+    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
+    <!--Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限-->
+    <!--    允许启动前台服务。-->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    <!--    允许请求忽略电池优化,以确保后台任务的持续运行。-->
+    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+    <!--    允许访问电池统计信息。-->
+    <uses-permission
+        android:name="android.permission.BATTERY_STATS"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许发布通知。-->
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+    <!--    允许取消通知。-->
+    <uses-permission android:name="android.permission.CANCEL_NOTIFICATIONS " />
+    <!--    允许应用绑定为通知监听服务。-->
+    <uses-permission
+        android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许跳转到通知监听设置界面-->
+    <uses-permission android:name="android.permission.ACTION_NOTIFICATION_LISTENER_SETTINGS" />
+    <!--    允许应用安装或卸载其他应用。-->
+    <uses-permission
+        android:name="android.permission.INSTALL_PACKAGES"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许修改系统设置。-->
+    <uses-permission
+        android:name="android.permission.WRITE_SETTINGS"
+        tools:ignore="ProtectedPermissions" />
+    <!--进程杀死-->
+    <!--    允许终止其他应用程序的后台进程。-->
+    <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
+    <!--    允许读取系统日志(受限权限)。-->
+    <uses-permission
+        android:name="android.permission.READ_LOGS"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许重启设备。-->
+    <uses-permission
+        android:name="android.permission.REBOOT"
+        tools:ignore="ProtectedPermissions" />
+    <!--    允许访问蓝牙。-->
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <!--    允许管理蓝牙设备(如打开、关闭蓝牙)。-->
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <!--    允许连接蓝牙设备。-->
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+    <!--    允许扫描蓝牙设备-->
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <!--    允许广播蓝牙设备信息。-->
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+
+    <application
+        android:name=".App"
+        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:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.FirstApp"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/App.kt b/app/src/main/java/com/example/firstapp/App.kt
new file mode 100644
index 0000000..93f6053
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/App.kt
@@ -0,0 +1,455 @@
+package com.example.firstapp
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.app.PendingIntent
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.location.Geocoder
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+import android.os.Build
+import androidx.lifecycle.MutableLiveData
+import androidx.multidex.MultiDex
+import androidx.work.Configuration
+import androidx.work.WorkManager
+import com.gyf.cactus.Cactus
+import com.gyf.cactus.callback.CactusCallback
+import com.gyf.cactus.ext.cactus
+import com.hjq.language.MultiLanguages
+import com.hjq.language.OnLanguageListener
+import com.example.firstapp.core.Core
+import com.example.firstapp.database.repository.CodeRepository
+import com.example.firstapp.database.repository.MsgRepository
+import com.example.firstapp.receiver.CactusReceiver
+import com.example.firstapp.service.BluetoothScanService
+import com.example.firstapp.service.HttpServerService
+import com.example.firstapp.utils.ACTION_START
+import com.example.firstapp.utils.AppInfo
+import com.example.firstapp.utils.CactusSave
+import com.example.firstapp.utils.FRONT_CHANNEL_ID
+import com.example.firstapp.utils.FRONT_CHANNEL_NAME
+import com.example.firstapp.utils.FRONT_NOTIFY_ID
+import com.example.firstapp.utils.FRPC_LIB_VERSION
+import com.example.firstapp.utils.HistoryUtils
+import com.example.firstapp.utils.Log
+import com.example.firstapp.utils.SettingUtils
+import com.example.firstapp.utils.SharedPreference
+
+import com.example.firstapp.utils.tinker.TinkerLoadLibrary
+import com.king.location.LocationClient
+import com.xuexiang.xutil.file.FileUtils
+import frpclib.Frpclib
+import io.reactivex.Observable
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.TimeUnit
+
+@Suppress("DEPRECATION")
+class App : Application(), CactusCallback, Configuration.Provider by Core {
+
+    val applicationScope = CoroutineScope(SupervisorJob())
+    val database by lazy { AppDatabase.getInstance(this) }
+    val msgRepository by lazy { MsgRepository(database.msgDao()) }
+    val codeRepository by lazy { CodeRepository(database.codeDao()) }
+
+    companion object {
+        const val TAG: String = "SmsForwarder"
+
+        @SuppressLint("StaticFieldLeak")
+        lateinit var context: Context
+
+        //通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
+        var CALL_TYPE_MAP: MutableMap<String, String> = mutableMapOf()
+        var FILED_MAP: MutableMap<String, String> = mutableMapOf()
+        var CHECK_MAP: MutableMap<String, String> = mutableMapOf()
+        var SIM_SLOT_MAP: MutableMap<String, String> = mutableMapOf()
+        var FORWARD_STATUS_MAP: MutableMap<Int, String> = mutableMapOf()
+        var BARK_LEVEL_MAP: MutableMap<String, String> = mutableMapOf()
+        var BARK_ENCRYPTION_ALGORITHM_MAP: MutableMap<String, String> = mutableMapOf()
+
+        //已插入SIM卡信息
+//        var SimInfoList: MutableMap<Int, SimInfo> = mutableMapOf()
+
+        //已安装App信息
+        var LoadingAppList = false
+        var UserAppList: MutableList<AppInfo> = mutableListOf()
+        var SystemAppList: MutableList<AppInfo> = mutableListOf()
+
+        /**
+         * @return 当前app是否是调试开发模式
+         */
+//        var isDebug: Boolean = BuildConfig.DEBUG
+        var isDebug: Boolean = true
+
+        //Cactus相关
+        val mEndDate = MutableLiveData<String>() //结束时间
+        val mLastTimer = MutableLiveData<String>() //上次存活时间
+        val mTimer = MutableLiveData<String>() //存活时间
+        val mStatus = MutableLiveData<Boolean>().apply { value = true } //运行状态
+        var mDisposable: Disposable? = null
+
+        //Location相关
+        val LocationClient by lazy { LocationClient(context) }
+        val Geocoder by lazy { Geocoder(context) }
+        val DateFormat by lazy { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) }
+
+        //Frpclib是否已经初始化
+        var FrpclibInited = false
+
+        //是否需要在拼接字符串时添加空格
+        var isNeedSpaceBetweenWords = false
+    }
+
+    override fun attachBaseContext(base: Context) {
+        //super.attachBaseContext(base)
+        // 绑定语种
+        super.attachBaseContext(MultiLanguages.attach(base))
+        //解决4.x运行崩溃的问题
+        MultiDex.install(this)
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+
+        // 设置全局异常捕获
+        val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
+        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
+            throwable.printStackTrace()
+            try {
+                val logPath = this.cacheDir.absolutePath + "/logs"
+                val logDir = File(logPath)
+                if (!logDir.exists()) logDir.mkdirs()
+                val dateFormat = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault())
+                val currentDateTime = dateFormat.format(Date())
+                val logFile = File(logPath, "crash_$currentDateTime.txt")
+                BufferedWriter(FileWriter(logFile, true)).use { writer ->
+                    writer.append("$throwable\n")
+                }
+            } catch (ex: IOException) {
+                ex.printStackTrace()
+            }
+            //使用默认的处理方式让APP停止运行
+            defaultHandler?.uncaughtException(thread, throwable)
+        }
+
+        try {
+            context = applicationContext
+            initLibs()
+
+            //纯客户端模式
+            if (SettingUtils.enablePureClientMode) return
+
+            //初始化WorkManager
+            WorkManager.initialize(this, Configuration.Builder().build())
+
+            //动态加载FrpcLib
+            val libPath = filesDir.absolutePath + "/libs"
+            val soFile = File(libPath)
+            if (soFile.exists()) {
+                try {
+                    TinkerLoadLibrary.installNativeLibraryPath(classLoader, soFile)
+                    FrpclibInited = FileUtils.isFileExists(filesDir.absolutePath + "/libs/libgojni.so") && FRPC_LIB_VERSION == Frpclib.getVersion()
+                } catch (throwable: Throwable) {
+                    Log.e("APP", throwable.message.toString())
+                }
+            }
+
+            //启动前台服务
+//            val foregroundServiceIntent = Intent(this, ForegroundService::class.java)
+//            foregroundServiceIntent.action = ACTION_START
+//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+//                startForegroundService(foregroundServiceIntent)
+//            } else {
+//                startService(foregroundServiceIntent)
+//            }
+
+            //启动HttpServer
+//            if (HttpServerUtils.enableServerAutorun) {
+//                Intent(this, HttpServerService::class.java).also {
+//                    startService(it)
+//                }
+//            }
+
+            //启动LocationService
+//            if (SettingUtils.enableLocation) {
+//                val locationServiceIntent = Intent(this, LocationService::class.java)
+//                locationServiceIntent.action = ACTION_START
+//                startService(locationServiceIntent)
+//            }
+
+            //监听电量&充电状态变化
+     /*       val batteryReceiver = BatteryReceiver()
+            val batteryFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
+            registerReceiver(batteryReceiver, batteryFilter)*/
+
+            //监听蓝牙状态变化
+            /*val bluetoothReceiver = BluetoothReceiver()
+            val filter = IntentFilter().apply {
+                addAction(BluetoothDevice.ACTION_FOUND)
+                addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)
+                addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
+                addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
+                addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)
+                addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
+                addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+                addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
+                addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
+            }
+            registerReceiver(bluetoothReceiver, filter)
+            if (SettingUtils.enableBluetooth) {
+                val bluetoothScanServiceIntent = Intent(this, BluetoothScanService::class.java)
+                bluetoothScanServiceIntent.action = ACTION_START
+                startService(bluetoothScanServiceIntent)
+            }*/
+
+            //监听网络变化
+           /* val networkReceiver = NetworkChangeReceiver()
+            val networkFilter = IntentFilter().apply {
+                addAction(ConnectivityManager.CONNECTIVITY_ACTION)
+                addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
+                addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION)
+                //addAction("android.intent.action.DATA_CONNECTION_STATE_CHANGED")
+            }
+            registerReceiver(networkReceiver, networkFilter)*/
+
+            //监听锁屏&解锁
+          /*  val lockScreenReceiver = LockScreenReceiver()
+            val lockScreenFilter = IntentFilter().apply {
+                addAction(Intent.ACTION_SCREEN_OFF)
+                addAction(Intent.ACTION_SCREEN_ON)
+                addAction(Intent.ACTION_USER_PRESENT)
+            }
+            registerReceiver(lockScreenReceiver, lockScreenFilter)*/
+
+            //Cactus 集成双进程前台服务,JobScheduler,onePix(一像素),WorkManager,无声音乐
+            if (SettingUtils.enableCactus) {
+                //注册广播监听器
+                registerReceiver(CactusReceiver(), IntentFilter().apply {
+                    addAction(Cactus.CACTUS_WORK)
+                    addAction(Cactus.CACTUS_STOP)
+                    addAction(Cactus.CACTUS_BACKGROUND)
+                    addAction(Cactus.CACTUS_FOREGROUND)
+                })
+                //设置通知栏点击事件
+                val activityIntent = Intent(this, MainActivity::class.java)
+                val flags = if (Build.VERSION.SDK_INT >= 30) PendingIntent.FLAG_IMMUTABLE else PendingIntent.FLAG_UPDATE_CURRENT
+                val pendingIntent = PendingIntent.getActivity(this, 0, activityIntent, flags)
+                cactus {
+                    setServiceId(FRONT_NOTIFY_ID) //服务Id
+                    setChannelId(FRONT_CHANNEL_ID) //渠道Id
+                    setChannelName(FRONT_CHANNEL_NAME) //渠道名
+                    setTitle(getString(R.string.app_name))
+                    setContent(SettingUtils.notifyContent)
+//                    setSmallIcon(R.drawable.ic_forwarder)
+                    setLargeIcon(R.mipmap.ic_launcher)
+                    setPendingIntent(pendingIntent)
+                    //无声音乐
+                    if (SettingUtils.enablePlaySilenceMusic) {
+                        setMusicEnabled(true)
+                        setBackgroundMusicEnabled(true)
+//                        setMusicId(R.raw.silence)
+                        //设置音乐间隔时间,时间间隔越长,越省电
+                        setMusicInterval(10)
+                        isDebug(true)
+                    }
+                    //是否可以使用一像素,默认可以使用,只有在android p以下可以使用
+                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P && SettingUtils.enableOnePixelActivity) {
+                        setOnePixEnabled(true)
+                    }
+                    //奔溃是否可以重启用户界面
+                    setCrashRestartUIEnabled(true)
+                    addCallback({
+                        Log.d(TAG, "Cactus保活:onStop回调")
+                    }) {
+                        Log.d(TAG, "Cactus保活:doWork回调")
+                    }
+                    //切后台切换回调
+                    addBackgroundCallback {
+                        Log.d(TAG, if (it) "SmsForwarder 切换到后台运行" else "SmsForwarder 切换到前台运行")
+                    }
+                }
+            }
+
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Log.e(TAG, "onCreate: $e")
+        }
+    }
+
+    /**
+     * 初始化基础库
+     */
+    private fun initLibs() {
+        Core.init(this)
+        Log.init(applicationContext)
+        // 配置文件初始化
+       /* SharedPreference.init(applicationContext)
+        // X系列基础库初始化
+//        XBasicLibInit.init(this)
+        // 初始化日志打印
+        isDebug = SettingUtils.enableDebugMode
+        Log.init(applicationContext)
+        // 转发历史工具类初始化
+        HistoryUtils.init(applicationContext)
+        // 版本更新初始化
+//        XUpdateInit.init(this)
+        // 运营统计数据
+//        UMengInit.init(this)
+        // 初始化语种切换框架
+        MultiLanguages.init(this)
+        // 设置语种变化监听器
+        MultiLanguages.setOnLanguageListener(object : OnLanguageListener {
+            override fun onAppLocaleChange(oldLocale: Locale, newLocale: Locale) {
+                // 注意:只有setAppLanguage时触发,clearAppLanguage时不触发
+                Log.i(TAG, "监听到应用切换了语种,旧语种:$oldLocale,新语种:$newLocale")
+                switchLanguage(newLocale)
+            }
+
+            override fun onSystemLocaleChange(oldLocale: Locale, newLocale: Locale) {
+                Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale")
+                switchLanguage(newLocale)
+                *//*val isFlowSystem = SettingUtils.isFlowSystemLanguage //MultiLanguages.isSystemLanguage(context)取值不对,一直是false
+                Log.i(TAG, "监听到系统切换了语种,旧语种:$oldLocale,新语种:$newLocale,是否跟随系统:$isFlowSystem")
+                if (isFlowSystem) {
+                    CommonUtils.switchLanguage(oldLocale, newLocale)
+                }*//*
+            }
+        })
+        switchLanguage(MultiLanguages.getAppLanguage(this))*/
+    }
+
+    @SuppressLint("CheckResult")
+    override fun doWork(times: Int) {
+      /*  Log.d(TAG, "doWork:$times")
+        mStatus.postValue(true)
+        val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
+        dateFormat.timeZone = TimeZone.getTimeZone("GMT+00:00")
+        var oldTimer = CactusSave.timer
+        if (times == 1) {
+            CactusSave.lastTimer = oldTimer
+            CactusSave.endDate = CactusSave.date
+            oldTimer = 0L
+        }
+        mLastTimer.postValue(dateFormat.format(Date(CactusSave.lastTimer * 1000)))
+        mEndDate.postValue(CactusSave.endDate)
+        mDisposable = Observable.interval(1, TimeUnit.SECONDS).map {
+            oldTimer + it
+        }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe { aLong ->
+            CactusSave.timer = aLong
+            CactusSave.date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).run {
+                format(Date())
+            }
+            mTimer.value = dateFormat.format(Date(aLong * 1000))
+        }*/
+    }
+
+    override fun onStop() {
+        Log.d(TAG, "onStop")
+        mStatus.postValue(false)
+        mDisposable?.apply {
+            if (!isDisposed) {
+                dispose()
+            }
+        }
+    }
+
+    //多语言切换时枚举常量自动切换语言
+//    private fun switchLanguage(newLocale: Locale) {
+//        isNeedSpaceBetweenWords = !newLocale.language.contains("zh")
+//
+//        CALL_TYPE_MAP.clear()
+//        CALL_TYPE_MAP.putAll(
+//            mapOf(
+//                //"0" to getString(R.string.unknown_call),
+//                "1" to getString(R.string.incoming_call_ended),
+//                "2" to getString(R.string.outgoing_call_ended),
+//                "3" to getString(R.string.missed_call),
+//                "4" to getString(R.string.incoming_call_received),
+//                "5" to getString(R.string.incoming_call_answered),
+//                "6" to getString(R.string.outgoing_call_started),
+//            )
+//        )
+//
+//        FILED_MAP.clear()
+//        FILED_MAP.putAll(
+//            mapOf(
+//                "transpond_all" to getString(R.string.rule_transpond_all),
+//                "phone_num" to getString(R.string.rule_phone_num),
+//                "msg_content" to getString(R.string.rule_msg_content),
+//                "multi_match" to getString(R.string.rule_multi_match),
+//                "package_name" to getString(R.string.rule_package_name),
+//                "inform_content" to getString(R.string.rule_inform_content),
+//                "call_type" to getString(R.string.rule_call_type),
+//                "uid" to getString(R.string.rule_uid),
+//            )
+//        )
+//
+//        CHECK_MAP.clear()
+//        CHECK_MAP.putAll(
+//            mapOf(
+//                "is" to getString(R.string.rule_is),
+//                "notis" to getString(R.string.rule_notis),
+//                "contain" to getString(R.string.rule_contain),
+//                "startwith" to getString(R.string.rule_startwith),
+//                "endwith" to getString(R.string.rule_endwith),
+//                "notcontain" to getString(R.string.rule_notcontain),
+//                "regex" to getString(R.string.rule_regex),
+//            )
+//        )
+//
+//        SIM_SLOT_MAP.clear()
+//        SIM_SLOT_MAP.putAll(
+//            mapOf(
+//                "ALL" to getString(R.string.rule_any),
+//                "SIM1" to "SIM1",
+//                "SIM2" to "SIM2",
+//            )
+//        )
+//
+//        FORWARD_STATUS_MAP.clear()
+//        FORWARD_STATUS_MAP.putAll(
+//            mapOf(
+//                0 to getString(R.string.failed),
+//                1 to getString(R.string.processing),
+//                2 to getString(R.string.success),
+//            )
+//        )
+//
+//        BARK_LEVEL_MAP.clear()
+//        BARK_LEVEL_MAP.putAll(
+//            mapOf(
+//                "active" to getString(R.string.bark_level_active),
+//                "timeSensitive" to getString(R.string.bark_level_timeSensitive),
+//                "passive" to getString(R.string.bark_level_passive)
+//            )
+//        )
+//
+//        BARK_ENCRYPTION_ALGORITHM_MAP.clear()
+//        BARK_ENCRYPTION_ALGORITHM_MAP.putAll(
+//            mapOf(
+//                "none" to getString(R.string.bark_encryption_algorithm_none),
+//                "AES128/CBC/PKCS7Padding" to "AES128/CBC/PKCS7Padding",
+//                "AES128/ECB/PKCS7Padding" to "AES128/ECB/PKCS7Padding",
+//                "AES192/CBC/PKCS7Padding" to "AES192/CBC/PKCS7Padding",
+//                "AES192/ECB/PKCS7Padding" to "AES192/ECB/PKCS7Padding",
+//                "AES256/CBC/PKCS7Padding" to "AES256/CBC/PKCS7Padding",
+//                "AES256/ECB/PKCS7Padding" to "AES256/ECB/PKCS7Padding",
+//            )
+//        )
+//    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/MainActivity.kt b/app/src/main/java/com/example/firstapp/MainActivity.kt
new file mode 100644
index 0000000..9c9052d
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/MainActivity.kt
@@ -0,0 +1,131 @@
+package com.example.firstapp
+
+import android.content.IntentFilter
+import android.os.Bundle
+import android.provider.Telephony
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import com.google.android.material.bottomnavigation.BottomNavigationView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
+import androidx.navigation.findNavController
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.setupActionBarWithNavController
+import androidx.navigation.ui.setupWithNavController
+import com.example.firstapp.databinding.ActivityMainBinding
+import com.example.firstapp.receiver.SmsReceiver
+import android.Manifest
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.firstapp.adapter.MyAdapter
+import com.example.firstapp.core.Core
+import com.example.firstapp.entity.Item
+import com.example.firstapp.ui.home.HomeViewModel
+
+class MainActivity : AppCompatActivity() {
+
+    private lateinit var binding: ActivityMainBinding
+
+    private var smsReceiver:SmsReceiver? = null
+
+    private lateinit var adapter: MyAdapter
+    private lateinit var homeViewModel: HomeViewModel
+
+
+    // 权限请求代码
+    private val permissionRequest = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
+        if (isGranted) {
+            // 权限授予后注册短信监听器
+            registerSmsReceiver()
+        } else {
+            // 权限拒绝,提示用户
+            Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        binding = ActivityMainBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+
+        // 在此位置初始化 homeViewModel
+        homeViewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
+
+        val navView: BottomNavigationView = binding.navView
+
+        val navController = findNavController(R.id.nav_host_fragment_activity_main)
+        // Passing each menu ID as a set of Ids because each
+        // menu should be considered as top level destinations.
+        val appBarConfiguration = AppBarConfiguration(
+            setOf(
+                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications
+            )
+        )
+        setupActionBarWithNavController(navController, appBarConfiguration)
+        navView.setupWithNavController(navController)
+
+        // 检查权限
+        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
+            // 请求权限
+            permissionRequest.launch(Manifest.permission.READ_SMS)
+        } else {
+            // 权限已经授予,继续执行相关操作
+            registerSmsReceiver()
+        }
+
+
+        // 模拟数据
+//        val items = listOf(
+//            Item(1, "First Item", "This is the first item description"),
+//            Item(2, "Second Item", "This is the second item description"),
+//            Item(3, "Third Item", "This is the third item description")
+//        )
+//
+//        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
+//        recyclerView.layoutManager = LinearLayoutManager(this)
+//        recyclerView.adapter = MyAdapter(items)
+
+//        var codeList = Core.code.getAllDesc()
+        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
+        recyclerView.layoutManager = LinearLayoutManager(this)
+//        recyclerView.adapter = MyAdapter(codeList)
+
+        // 初始化适配器
+        adapter = MyAdapter()
+        recyclerView.adapter = adapter
+
+        // 观察 LiveData 数据
+        homeViewModel.codeList.observe(this) { codeList ->
+            // 如果 codeList 为 null,避免闪退
+            if (codeList != null) {
+                adapter.submitList(codeList)
+                // 滚动到顶部
+                recyclerView.scrollToPosition(0)
+            } else {
+                // 如果数据为空,可以显示空列表或其他处理
+                Toast.makeText(this, "No data available", Toast.LENGTH_SHORT).show()
+            }
+        }
+
+        // 注册广播接收器来监听数据更新
+        val filter = IntentFilter("com.example.firstapp.DATA_UPDATED")
+        registerReceiver(object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                // 数据已更新,刷新 LiveData
+                homeViewModel.loadData()
+            }
+        }, filter)
+
+    }
+
+    private fun registerSmsReceiver() {
+        smsReceiver = SmsReceiver()
+        val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION)
+        registerReceiver(smsReceiver, filter)
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt
new file mode 100644
index 0000000..d15ce6a
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt
@@ -0,0 +1,42 @@
+package com.example.firstapp.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.example.firstapp.R
+import com.example.firstapp.database.entity.Code
+import androidx.recyclerview.widget.ListAdapter
+
+
+class MyAdapter : ListAdapter<Code, MyAdapter.ViewHolder>(CodeDiffCallback()) {
+
+    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val tvTitle: TextView = view.findViewById(R.id.tvTitle)
+        val tvDescription: TextView = view.findViewById(R.id.tvDescription)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_layout, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = getItem(position) // 使用 getItem 来获取当前位置的 item
+        holder.tvTitle.text = item.type // 假设 Code 类有一个 `type` 属性
+        holder.tvDescription.text = item.code // 假设 Code 类有一个 `code` 属性
+    }
+
+    // 使用 DiffUtil 来优化列表更新
+    class CodeDiffCallback : androidx.recyclerview.widget.DiffUtil.ItemCallback<Code>() {
+        override fun areItemsTheSame(oldItem: Code, newItem: Code): Boolean {
+            return oldItem.id == newItem.id // 假设 Code 类有唯一的 id 字段
+        }
+
+        override fun areContentsTheSame(oldItem: Code, newItem: Code): Boolean {
+            return oldItem == newItem // 如果内容相同,返回 true
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt b/app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt
new file mode 100644
index 0000000..caea796
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt
@@ -0,0 +1,32 @@
+package com.example.firstapp.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.example.firstapp.R
+import com.example.firstapp.entity.Item
+
+class MyAdapter2(private val items: List<Item>) :
+    RecyclerView.Adapter<MyAdapter2.ViewHolder>() {
+
+    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val tvTitle: TextView = view.findViewById(R.id.tvTitle)
+        val tvDescription: TextView = view.findViewById(R.id.tvDescription)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_layout, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = items[position]
+        holder.tvTitle.text = item.title
+        holder.tvDescription.text = item.description
+    }
+
+    override fun getItemCount() = items.size
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt b/app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt
new file mode 100644
index 0000000..33404e9
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt
@@ -0,0 +1,33 @@
+package com.example.firstapp.adapter
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.example.firstapp.R
+import com.example.firstapp.database.entity.Code
+import com.example.firstapp.entity.Item
+
+class MyAdapter_bak(private val items: List<Code>) :
+    RecyclerView.Adapter<MyAdapter_bak.ViewHolder>() {
+
+    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+        val tvTitle: TextView = view.findViewById(R.id.tvTitle)
+        val tvDescription: TextView = view.findViewById(R.id.tvDescription)
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.item_layout, parent, false)
+        return ViewHolder(view)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = items[position]
+        holder.tvTitle.text = item.type
+        holder.tvDescription.text = item.code
+    }
+
+    override fun getItemCount() = items.size
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/core/Core.kt b/app/src/main/java/com/example/firstapp/core/Core.kt
new file mode 100644
index 0000000..f1f55b4
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/core/Core.kt
@@ -0,0 +1,29 @@
+package com.example.firstapp.core
+
+import android.app.Application
+import androidx.work.Configuration
+import com.example.firstapp.App
+import com.example.firstapp.database.repository.CodeRepository
+import com.example.firstapp.database.repository.MsgRepository
+
+import kotlinx.coroutines.launch
+
+object Core : Configuration.Provider {
+    lateinit var app: Application
+
+    val msg: MsgRepository by lazy { (app as App).msgRepository }
+    val code: CodeRepository by lazy { (app as App).codeRepository }
+
+    fun init(app: Application) {
+        this.app = app
+    }
+
+    override fun getWorkManagerConfiguration(): Configuration {
+        return Configuration.Builder().apply {
+            setDefaultProcessName(app.packageName + ":bg")
+//            setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO)
+            setExecutor { (app as App).applicationScope.launch { it.run() } }
+            setTaskExecutor { (app as App).applicationScope.launch { it.run() } }
+        }.build()
+    }
+}
diff --git a/app/src/main/java/com/example/firstapp/database/AppDatabase.kt b/app/src/main/java/com/example/firstapp/database/AppDatabase.kt
new file mode 100644
index 0000000..42a1399
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/AppDatabase.kt
@@ -0,0 +1,104 @@
+package com.example.firstapp
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.example.firstapp.database.dao.CodeDao
+import com.example.firstapp.database.dao.MsgDao
+import com.example.firstapp.database.entity.Code
+import com.example.firstapp.database.entity.Msg
+import com.example.firstapp.utils.DATABASE_NAME
+import com.example.firstapp.utils.SettingUtils
+import com.example.firstapp.utils.TAG_LIST
+
+import com.example.firstapp.database.ext.ConvertersDate
+
+
+
+@Database(
+    entities = [ Msg::class, Code::class],
+//    views = [LogsDetail::class],
+    version = 19,
+    exportSchema = false
+)
+@TypeConverters(ConvertersDate::class)
+abstract class AppDatabase : RoomDatabase() {
+    abstract fun msgDao(): MsgDao
+    abstract fun codeDao(): CodeDao
+
+    companion object {
+        @Volatile
+        private var instance: AppDatabase? = null
+
+        fun getInstance(context: Context): AppDatabase {
+            return instance ?: synchronized(this) {
+                instance ?: buildDatabase(context).also { instance = it }
+            }
+        }
+
+        private fun buildDatabase(context: Context): AppDatabase {
+            val builder = Room.databaseBuilder(
+                context.applicationContext, AppDatabase::class.java, DATABASE_NAME
+            ).allowMainThreadQueries() //TODO:允许主线程访问,后面再优化
+                .addCallback(object : Callback() {
+                    override fun onCreate(db: SupportSQLiteDatabase) {
+
+                    }
+                }).addMigrations(
+
+                    MIGRATION_MSG,
+                )
+
+            /*if (BuildConfig.DEBUG) {
+                builder.setQueryCallback({ sqlQuery, bindArgs ->
+                    println("SQL_QUERY: $sqlQuery\nBIND_ARGS: $bindArgs")
+                }, Executors.newSingleThreadExecutor())
+            }*/
+
+            return builder.build()
+        }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        private val MIGRATION_MSG = object : Migration(19, 20) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                //database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
+                database.execSQL(
+                    """
+CREATE TABLE "Msg" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" TEXT NOT NULL DEFAULT 'sms',
+  "from" TEXT NOT NULL DEFAULT '',
+  "content" TEXT NOT NULL DEFAULT '',
+  "sim_slot" INTEGER NOT NULL DEFAULT -1,
+  "sim_info" TEXT NOT NULL DEFAULT '',
+  "sub_id" INTEGER NOT NULL DEFAULT 0,
+  "time" INTEGER NOT NULL
+)
+""".trimIndent()
+                )
+
+                database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
+
+            }
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/com/example/firstapp/database/AppDatabase_bak.kt b/app/src/main/java/com/example/firstapp/database/AppDatabase_bak.kt
new file mode 100644
index 0000000..0332822
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/AppDatabase_bak.kt
@@ -0,0 +1,442 @@
+package com.example.firstapp
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+import com.example.firstapp.database.dao.MsgDao
+import com.example.firstapp.database.entity.Msg
+import com.example.firstapp.utils.DATABASE_NAME
+import com.example.firstapp.utils.SettingUtils
+import com.example.firstapp.utils.TAG_LIST
+
+import com.example.firstapp.database.ext.ConvertersDate
+
+
+
+@Database(
+    entities = [ Msg::class],
+//    views = [LogsDetail::class],
+    version = 19,
+    exportSchema = false
+)
+@TypeConverters(ConvertersDate::class)
+abstract class AppDatabase_bak : RoomDatabase() {
+    abstract fun msgDao(): MsgDao
+    companion object {
+        @Volatile
+        private var instance: AppDatabase_bak? = null
+
+        fun getInstance(context: Context): AppDatabase_bak {
+            return instance ?: synchronized(this) {
+                instance ?: buildDatabase(context).also { instance = it }
+            }
+        }
+
+        private fun buildDatabase(context: Context): AppDatabase_bak {
+            val builder = Room.databaseBuilder(
+                context.applicationContext, AppDatabase_bak::class.java, DATABASE_NAME
+            ).allowMainThreadQueries() //TODO:允许主线程访问,后面再优化
+                .addCallback(object : Callback() {
+                    override fun onCreate(db: SupportSQLiteDatabase) {
+                        //fillInDb(context.applicationContext)
+                        db.execSQL(
+                            """
+INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '[common]
+#frps服务端公网IP
+server_addr = 88.88.88.88
+#frps服务端公网端口
+server_port = 8888
+#可选,建议启用
+token = 88888888
+#连接服务端的超时时间(增大时间避免frpc在网络未就绪的情况下启动失败)
+dial_server_timeout = 60
+#第一次登陆失败后是否退出
+login_fail_exit = false
+
+#[二选一即可]每台机器不可重复,通过 http://88.88.88.88:5000 访问
+[SmsForwarder-TCP]
+type = tcp
+local_ip = 127.0.0.1
+local_port = 5000
+#只要修改下面这一行(frps所在服务器必须暴露的公网端口)
+remote_port = 5000
+
+#[二选一即可]每台机器不可重复,通过 http://smsf.demo.com 访问
+[SmsForwarder-HTTP]
+type = http
+local_ip = 127.0.0.1
+local_port = 5000
+#只要修改下面这一行(在frps端将域名反代到vhost_http_port)
+custom_domains = smsf.demo.com
+', 0, '1651334400000')
+""".trimIndent()
+                        )
+                    }
+                }).addMigrations(
+                    MIGRATION_1_2,
+                    MIGRATION_2_3,
+                    MIGRATION_3_4,
+                    MIGRATION_4_5,
+                    MIGRATION_5_6,
+                    MIGRATION_6_7,
+                    MIGRATION_7_8,
+                    MIGRATION_8_9,
+                    MIGRATION_9_10,
+                    MIGRATION_10_11,
+                    MIGRATION_11_12,
+                    MIGRATION_12_13,
+                    MIGRATION_13_14,
+                    MIGRATION_14_15,
+                    MIGRATION_15_16,
+                    MIGRATION_16_17,
+                    MIGRATION_17_18,
+                    MIGRATION_18_19,
+                )
+
+            /*if (BuildConfig.DEBUG) {
+                builder.setQueryCallback({ sqlQuery, bindArgs ->
+                    println("SQL_QUERY: $sqlQuery\nBIND_ARGS: $bindArgs")
+                }, Executors.newSingleThreadExecutor())
+            }*/
+
+            return builder.build()
+        }
+
+        //转发日志添加SIM卡槽信息
+        private val MIGRATION_1_2 = object : Migration(1, 2) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table log add column sim_info TEXT ")
+            }
+        }
+
+        //转发规则添加SIM卡槽信息
+        private val MIGRATION_2_3 = object : Migration(2, 3) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column sim_slot TEXT NOT NULL DEFAULT 'ALL' ")
+            }
+        }
+
+        //转发日志添加转发状态与返回信息
+        private val MIGRATION_3_4 = object : Migration(3, 4) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table log add column forward_status INTEGER NOT NULL DEFAULT 1 ")
+                database.execSQL("Alter table log add column forward_response TEXT NOT NULL DEFAULT 'ok' ")
+            }
+        }
+
+        //转发规则添加规则自定义信息模板
+        private val MIGRATION_4_5 = object : Migration(4, 5) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column sms_template TEXT NOT NULL DEFAULT '' ")
+            }
+        }
+
+        //增加转发规则与日志的分类
+        private val MIGRATION_5_6 = object : Migration(5, 6) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column type TEXT NOT NULL DEFAULT 'sms' ")
+                database.execSQL("Alter table log add column type TEXT NOT NULL DEFAULT 'sms' ")
+            }
+        }
+
+        //转发规则添加正则替换内容
+        private val MIGRATION_6_7 = object : Migration(6, 7) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column regex_replace TEXT NOT NULL DEFAULT '' ")
+            }
+        }
+
+        //更新日志表状态:0=失败,1=待处理,2=成功
+        private val MIGRATION_7_8 = object : Migration(7, 8) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("update log set forward_status = 2 where forward_status = 1 ")
+            }
+        }
+
+        //规则/通道状态:0=禁用,1=启用
+        private val MIGRATION_8_9 = object : Migration(8, 9) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column status INTEGER NOT NULL DEFAULT 1 ")
+                database.execSQL("update sender set status = 1 ")
+            }
+        }
+
+        //从SQLite迁移到 Room
+        private val MIGRATION_9_10 = object : Migration(9, 10) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL(
+                    """
+CREATE TABLE "Frpc" (
+  "uid" TEXT NOT NULL,
+  "name" TEXT NOT NULL,
+  "config" TEXT NOT NULL,
+  "autorun" INTEGER NOT NULL DEFAULT 0,
+  "time" INTEGER NOT NULL,
+  PRIMARY KEY ("uid")
+)
+""".trimIndent()
+                )
+                database.execSQL(
+                    """
+INSERT INTO "Frpc" VALUES ('830b0a0e-c2b3-4f95-b3c9-55db12923d2e', '远程控制SmsForwarder', '
+#frps服务端公网IP
+serverAddr = "88.88.88.88"
+#frps服务端公网端口
+serverPort = 8888
+#连接服务端的超时时间(增大时间避免frpc在网络未就绪的情况下启动失败)
+transport.dialServerTimeout = 60
+#第一次登陆失败后是否退出
+loginFailExit = false
+#可选,建议启用
+auth.method = "token"
+auth.token = "88888888"
+
+#[二选一即可]每台机器的 name 和 remotePort 不可重复,通过 http://88.88.88.88:5000 访问
+[[proxies]]
+#同一个frps下,多台设备的 name 不可重复
+name = "SmsForwarder-TCP-001"
+type = "tcp"
+localIP = "127.0.0.1"
+localPort = 5000
+#只要修改下面这一行(frps所在服务器必须暴露且防火墙放行的公网端口,同一个frps下不可重复)
+remotePort = 5000
+
+#[二选一即可]每台机器的 name 和 customDomains 不可重复,通过 http://smsf.demo.com 访问
+[[proxies]]
+#同一个frps下,多台设备的 name 不可重复
+name = "SmsForwarder-HTTP-001"
+type = "http"
+localPort = 5000
+#只要修改下面这一行(在frps端将域名反代到vhost_http_port)
+customDomains = ["smsf.demo.com"]
+
+', 0, '1651334400000')
+""".trimIndent()
+                )
+
+                database.execSQL("ALTER TABLE log RENAME TO old_log")
+                database.execSQL(
+                    """
+CREATE TABLE "Logs" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" TEXT NOT NULL DEFAULT 'sms',
+  "from" TEXT NOT NULL DEFAULT '',
+  "content" TEXT NOT NULL DEFAULT '',
+  "rule_id" INTEGER NOT NULL DEFAULT 0,
+  "sim_info" TEXT NOT NULL DEFAULT '',
+  "forward_status" INTEGER NOT NULL DEFAULT 1,
+  "forward_response" TEXT NOT NULL DEFAULT '',
+  "time" INTEGER NOT NULL,
+  FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+)
+""".trimIndent()
+                )
+                database.execSQL("CREATE UNIQUE INDEX \"index_Log_id\" ON \"Logs\" ( \"id\" ASC)")
+                database.execSQL("CREATE INDEX \"index_Log_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)")
+                database.execSQL("INSERT INTO Logs (id,type,`from`,content,sim_info,rule_id,forward_status,forward_response,time) SELECT _id,type,l_from,content,sim_info,rule_id,forward_status,forward_response,strftime('%s000',time) FROM old_log")
+                database.execSQL("DROP TABLE old_log")
+
+                database.execSQL("ALTER TABLE rule RENAME TO old_rule")
+                database.execSQL(
+                    """
+CREATE TABLE "Rule" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" TEXT NOT NULL DEFAULT 'sms',
+  "filed" TEXT NOT NULL DEFAULT 'transpond_all',
+  "check" TEXT NOT NULL DEFAULT 'is',
+  "value" TEXT NOT NULL DEFAULT '',
+  "sender_id" INTEGER NOT NULL DEFAULT 0,
+  "sms_template" TEXT NOT NULL DEFAULT '',
+  "regex_replace" TEXT NOT NULL DEFAULT '',
+  "sim_slot" TEXT NOT NULL DEFAULT 'ALL',
+  "status" INTEGER NOT NULL DEFAULT 1,
+  "time" INTEGER NOT NULL,
+  FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+)
+""".trimIndent()
+                )
+                database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)")
+                database.execSQL("CREATE INDEX \"index_Rule_sender_id\" ON \"Rule\" ( \"sender_id\" ASC)")
+                database.execSQL("INSERT INTO Rule (id,type,filed,`check`,value,sender_id,time,sms_template,regex_replace,status,sim_slot) SELECT _id,type,filed,tcheck,value,sender_id,strftime('%s000',time),sms_template,regex_replace,status,sim_slot FROM old_rule")
+                database.execSQL("DROP TABLE old_rule")
+
+                database.execSQL("ALTER TABLE sender RENAME TO old_sender")
+                database.execSQL(
+                    """
+CREATE TABLE "Sender" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" INTEGER NOT NULL DEFAULT 1,
+  "name" TEXT NOT NULL DEFAULT '',
+  "json_setting" TEXT NOT NULL DEFAULT '',
+  "status" INTEGER NOT NULL DEFAULT 1,
+  "time" INTEGER NOT NULL
+)
+""".trimIndent()
+                )
+                database.execSQL("INSERT INTO Sender (id,name,status,type,json_setting,time) SELECT _id,name,status,type,json_setting,strftime('%s000',time) FROM old_sender")
+                database.execSQL("DROP TABLE old_sender")
+            }
+        }
+
+        //转发日志添加SIM卡槽ID
+        private val MIGRATION_10_11 = object : Migration(10, 11) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table Logs add column sub_id INTEGER NOT NULL DEFAULT 0")
+            }
+        }
+
+        //单个转发规则可绑定多个发送通道
+        private val MIGRATION_11_12 = object : Migration(11, 12) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table Logs add column sender_id INTEGER NOT NULL DEFAULT 0")
+                database.execSQL("Update Logs Set sender_id = (Select sender_id from Rule where Logs.rule_id = Rule.id)")
+                database.execSQL("Alter table Rule add column sender_list TEXT NOT NULL DEFAULT ''")
+                database.execSQL("Update Rule set sender_list = sender_id")
+                database.execSQL("CREATE INDEX \"index_Rule_sender_ids\" ON \"Rule\" ( \"sender_list\" ASC)")
+                //删除字段:sender_id
+                /*database.execSQL("Create table Rule_t as Select id,type,filed,check,value,sender_list,sms_template,regex_replace,sim_slot,status,time from Rule where 1 = 1")
+                database.execSQL("Drop table Rule")
+                database.execSQL("Alter table Rule_t rename to Rule")
+                database.execSQL("CREATE UNIQUE INDEX \"index_Rule_id\" ON \"Rule\" ( \"id\" ASC)")*/
+            }
+        }
+
+        //转发规则添加发送通道逻辑
+        private val MIGRATION_12_13 = object : Migration(12, 13) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table Rule add column sender_logic TEXT NOT NULL DEFAULT 'ALL'")
+            }
+        }
+
+        //分割Logs表
+        private val MIGRATION_13_14 = object : Migration(13, 14) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                //database.execSQL("Create table Msg as Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
+                database.execSQL(
+                    """
+CREATE TABLE "Msg" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" TEXT NOT NULL DEFAULT 'sms',
+  "from" TEXT NOT NULL DEFAULT '',
+  "content" TEXT NOT NULL DEFAULT '',
+  "sim_slot" INTEGER NOT NULL DEFAULT -1,
+  "sim_info" TEXT NOT NULL DEFAULT '',
+  "sub_id" INTEGER NOT NULL DEFAULT 0,
+  "time" INTEGER NOT NULL
+)
+""".trimIndent()
+                )
+                database.execSQL("INSERT INTO Msg (id,type,`from`,content,sim_slot,sim_info,sub_id,time) Select id,type,`from`,content,(case when sim_info like 'SIM1%' then '0' when sim_info like 'SIM2%' then '1' else '-1' end) as sim_slot,sim_info,sub_id,time from Logs where 1 = 1")
+                database.execSQL("CREATE UNIQUE INDEX \"index_Msg_id\" ON \"Msg\" ( \"id\" ASC)")
+                database.execSQL("ALTER TABLE Logs RENAME TO Logs_old")
+                //database.execSQL("Create table Logs_new as Select id,id as msg_id,rule_id,sender_id,forward_status,forward_response,time from Logs where 1 = 1")
+                database.execSQL(
+                    """
+CREATE TABLE "Logs" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" TEXT NOT NULL DEFAULT 'sms',
+  "msg_id" INTEGER NOT NULL DEFAULT 0,
+  "rule_id" INTEGER NOT NULL DEFAULT 0,
+  "sender_id" INTEGER NOT NULL DEFAULT 0,
+  "forward_status" INTEGER NOT NULL DEFAULT 1,
+  "forward_response" TEXT NOT NULL DEFAULT '',
+  "time" INTEGER NOT NULL,
+  FOREIGN KEY ("msg_id") REFERENCES "Msg" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+  FOREIGN KEY ("rule_id") REFERENCES "Rule" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+  FOREIGN KEY ("sender_id") REFERENCES "Sender" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);
+""".trimIndent()
+                )
+                database.execSQL("INSERT INTO Logs (id,type,msg_id,rule_id,sender_id,forward_status,forward_response,time) SELECT id,type,id as msg_id,rule_id,sender_id,forward_status,forward_response,time FROM Logs_old")
+                database.execSQL("DROP TABLE Logs_old")
+                database.execSQL("CREATE UNIQUE INDEX \"index_Logs_id\" ON \"Logs\" ( \"id\" ASC)")
+                database.execSQL("CREATE INDEX \"index_Logs_msg_id\" ON \"Logs\" ( \"msg_id\" ASC)")
+                database.execSQL("CREATE INDEX \"index_Logs_rule_id\" ON \"Logs\" ( \"rule_id\" ASC)")
+                database.execSQL("CREATE INDEX \"index_Logs_sender_id\" ON \"Logs\" ( \"sender_id\" ASC)")
+            }
+        }
+
+        // 定义数据库迁移配置
+        private val MIGRATION_14_15 = object : Migration(14, 15) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                // 这里新建一个视图(视图名称要用两个半角的间隔号括起来)
+                database.execSQL("CREATE VIEW `LogsDetail` AS SELECT LOGS.id,LOGS.type,LOGS.msg_id,LOGS.rule_id,LOGS.sender_id,LOGS.forward_status,LOGS.forward_response,LOGS.TIME,Rule.filed AS rule_filed,Rule.`check` AS rule_check,Rule.value AS rule_value,Rule.sim_slot AS rule_sim_slot,Sender.type AS sender_type,Sender.NAME AS sender_name FROM LOGS  LEFT JOIN Rule ON LOGS.rule_id = Rule.id LEFT JOIN Sender ON LOGS.sender_id = Sender.id")
+            }
+        }
+
+        //免打扰(禁用转发)时间段
+        private val MIGRATION_15_16 = object : Migration(15, 16) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table rule add column silent_period_start INTEGER NOT NULL DEFAULT 0 ")
+                database.execSQL("Alter table rule add column silent_period_end INTEGER NOT NULL DEFAULT 0 ")
+            }
+        }
+
+        //通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
+        private val MIGRATION_16_17 = object : Migration(16, 17) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL("Alter table Msg add column call_type INTEGER NOT NULL DEFAULT 0")
+            }
+        }
+
+        //自动化任务
+        private val MIGRATION_17_18 = object : Migration(17, 18) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                database.execSQL(
+                    """
+CREATE TABLE "Task" (
+  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+  "type" INTEGER NOT NULL DEFAULT 1,
+  "name" TEXT NOT NULL DEFAULT '',
+  "description" TEXT NOT NULL DEFAULT '',
+  "conditions" TEXT NOT NULL DEFAULT '',
+  "actions" TEXT NOT NULL DEFAULT '',
+  "last_exec_time" INTEGER NOT NULL,
+  "next_exec_time" INTEGER NOT NULL,
+  "status" INTEGER NOT NULL DEFAULT 1
+)
+""".trimIndent()
+                )
+            }
+        }
+
+        //自定义模板可用变量统一成英文标签
+        private val MIGRATION_18_19 = object : Migration(18, 19) {
+            override fun migrate(database: SupportSQLiteDatabase) {
+                //替换自定义模板标签
+                var smsTemplate = SettingUtils.smsTemplate
+                //替换Rule.sms_template中的标签
+                var ruleColumnCN = "sms_template"
+                var ruleColumnTW = "sms_template"
+                //替换Sender.json_setting中的标签
+                var senderColumnCN = "json_setting"
+                var senderColumnTW = "json_setting"
+
+                for (i in TAG_LIST.indices) {
+                    val tagCN = TAG_LIST[i]["zh_CN"].toString()
+                    val tagTW = TAG_LIST[i]["zh_TW"].toString()
+                    val tagEN = TAG_LIST[i]["en"].toString()
+                    smsTemplate = smsTemplate.replace(tagCN, tagEN)
+                    ruleColumnCN = "REPLACE($ruleColumnCN, '$tagCN', '$tagEN')"
+                    ruleColumnTW = "REPLACE($ruleColumnTW, '$tagTW', '$tagEN')"
+                    senderColumnCN = "REPLACE($senderColumnCN, '$tagCN', '$tagEN')"
+                    senderColumnTW = "REPLACE($senderColumnTW, '$tagTW', '$tagEN')"
+                }
+
+                database.execSQL("UPDATE Rule SET sms_template = $ruleColumnCN WHERE sms_template != ''")
+                database.execSQL("UPDATE Rule SET sms_template = $ruleColumnTW WHERE sms_template != ''")
+
+                database.execSQL("UPDATE Sender SET json_setting = $senderColumnCN WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
+                database.execSQL("UPDATE Sender SET json_setting = $senderColumnTW WHERE type NOT IN (4, 5, 6, 7, 8, 14)")
+
+                SettingUtils.smsTemplate = smsTemplate
+            }
+        }
+
+    }
+
+}
diff --git a/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt b/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
new file mode 100644
index 0000000..8df5a2b
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
@@ -0,0 +1,44 @@
+package com.example.firstapp.database.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Update
+import com.example.firstapp.database.entity.Code
+import com.example.firstapp.database.entity.Msg
+import io.reactivex.Completable
+
+@Dao
+interface CodeDao {
+
+    @Insert
+     fun insert(code: Code): Long
+
+    @Update
+     fun update(code: Code)
+
+    @Delete
+    fun delete(msg: Msg): Completable
+
+    @Query("DELETE FROM Code where id=:id")
+    fun delete(id: Long)
+
+
+    @Query("SELECT * FROM Code WHERE id = :id LIMIT 1")
+     fun getCodeById(id: Long): Code?
+
+    @Query("SELECT * FROM Code")
+     fun getAllCodes(): List<Code>
+
+    @Query("SELECT * FROM Code WHERE type = :type")
+     fun getCodesByType(type: String): List<Code>
+
+
+
+    @Query("DELETE FROM Code WHERE id = :id")
+     fun deleteCodeById(id: Long)
+
+    @Query("SELECT * FROM Code order by time desc")
+    abstract fun getAllCodesDesc():  List<Code>
+}
diff --git a/app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt b/app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt
new file mode 100644
index 0000000..5d54b7b
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt
@@ -0,0 +1,57 @@
+package com.example.firstapp.database.dao
+
+
+import androidx.paging.PagingSource
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.room.Transaction
+import androidx.room.Update
+import androidx.sqlite.db.SupportSQLiteQuery
+import com.example.firstapp.database.entity.Msg
+
+import io.reactivex.Completable
+import io.reactivex.Single
+
+@Dao
+interface MsgDao {
+
+    @Insert(onConflict = OnConflictStrategy.IGNORE)
+    fun insert(msg: Msg): Long
+
+    @Delete
+    fun delete(msg: Msg): Completable
+
+    @Query("DELETE FROM Msg where id=:id")
+    fun delete(id: Long)
+
+    @RawQuery
+    fun deleteAll(sql: SupportSQLiteQuery): Int
+
+    @Query("DELETE FROM Msg")
+    fun deleteAll()
+
+    @Query("DELETE FROM Msg where time<:time")
+    fun deleteTimeAgo(time: Long)
+
+    @Update
+    fun update(msg: Msg): Completable
+
+    @Query("SELECT * FROM Msg where id=:id")
+    fun get(id: Long): Single<Msg>
+
+    @Query("SELECT count(*) FROM Msg where type=:type")
+    fun count(type: String): Single<Int>
+
+//    @Transaction
+//    @Query("SELECT * FROM Msg WHERE type = :type ORDER BY id DESC")
+//    fun pagingSource(type: String): PagingSource<Int, MsgAndLogs>
+//
+//    @Transaction
+//    @RawQuery(observedEntities = [MsgAndLogs::class])
+//    fun pagingSource(query: SupportSQLiteQuery): PagingSource<Int, MsgAndLogs>
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/database/entity/Code.kt b/app/src/main/java/com/example/firstapp/database/entity/Code.kt
new file mode 100644
index 0000000..24f5223
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/entity/Code.kt
@@ -0,0 +1,18 @@
+package com.example.firstapp.database.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.util.*
+
+@Entity(tableName = "Code")
+data class Code(
+    @PrimaryKey(autoGenerate = true) val id: Long = 0, // 自增长的 id
+    val category: String,
+    val categoryId: Long,
+    val type: String,
+    val typeId: Long,
+    val ruleId: Long,
+    val msgId: Long,
+    val code: String,
+    var time: Date = Date(),
+)
diff --git a/app/src/main/java/com/example/firstapp/database/entity/Msg.kt b/app/src/main/java/com/example/firstapp/database/entity/Msg.kt
new file mode 100644
index 0000000..ac5caf9
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/entity/Msg.kt
@@ -0,0 +1,45 @@
+package com.example.firstapp.database.entity
+
+import android.os.Parcelable
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import com.example.firstapp.R
+import kotlinx.parcelize.Parcelize
+import java.util.Date
+
+@Parcelize
+@Entity(
+    tableName = "Msg",
+    indices = [
+        Index(value = ["id"], unique = true)
+    ]
+)
+data class Msg(
+    @PrimaryKey(autoGenerate = true)
+    @ColumnInfo(name = "id") var id: Long,
+    @ColumnInfo(name = "type", defaultValue = "sms") var type: String,
+    @ColumnInfo(name = "from", defaultValue = "") var from: String,
+    @ColumnInfo(name = "content", defaultValue = "") var content: String,
+    @ColumnInfo(name = "sim_slot", defaultValue = "-1") var simSlot: Int = -1, //卡槽id:-1=获取失败、0=卡槽1、1=卡槽2
+    @ColumnInfo(name = "sim_info", defaultValue = "") var simInfo: String = "",
+    @ColumnInfo(name = "sub_id", defaultValue = "0") var subId: Int = 0,
+    //通话类型:1.来电挂机 2.去电挂机 3.未接来电 4.来电提醒 5.来电接通 6.去电拨出
+    @ColumnInfo(name = "call_type", defaultValue = "0") var callType: Int = 0,
+    @ColumnInfo(name = "time") var time: Date = Date(),
+) : Parcelable {
+
+    val simImageId: Int
+        get() {
+            return when {
+                type == "app" -> R.drawable.ic_app
+                simSlot == 0 -> R.drawable.ic_sim1
+                simSlot == 1 -> R.drawable.ic_sim2
+                simInfo.isNotEmpty() && simInfo.replace("-", "").startsWith("SIM2") -> R.drawable.ic_sim2
+                simInfo.isNotEmpty() && simInfo.replace("-", "").startsWith("SIM1") -> R.drawable.ic_sim1
+                else -> R.drawable.ic_sim
+            }
+        }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/database/ext/ConvertersDate.kt b/app/src/main/java/com/example/firstapp/database/ext/ConvertersDate.kt
new file mode 100644
index 0000000..14732bf
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/ext/ConvertersDate.kt
@@ -0,0 +1,16 @@
+package com.example.firstapp.database.ext
+
+import androidx.room.TypeConverter
+import java.util.Date
+
+class ConvertersDate {
+    @TypeConverter
+    fun fromTimestamp(value: Long?): Date? {
+        return value?.let { Date(it) }
+    }
+
+    @TypeConverter
+    fun dateToTimestamp(date: Date?): Long? {
+        return date?.time
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt b/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt
new file mode 100644
index 0000000..2d9eceb
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt
@@ -0,0 +1,21 @@
+package com.example.firstapp.database.repository
+
+import androidx.annotation.WorkerThread
+import com.example.firstapp.database.dao.CodeDao
+import com.example.firstapp.database.entity.Code
+
+
+class CodeRepository(private val codeDao: CodeDao) {
+
+    @WorkerThread
+    fun insert(code: Code): Long = codeDao.insert(code)
+
+    @WorkerThread
+    fun delete(id: Long) = codeDao.delete(id)
+
+    fun getAll() = codeDao.getAllCodes()
+
+    fun getAllDesc() = codeDao.getAllCodesDesc()
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/database/repository/MsgRepository.kt b/app/src/main/java/com/example/firstapp/database/repository/MsgRepository.kt
new file mode 100644
index 0000000..8d32cd3
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/database/repository/MsgRepository.kt
@@ -0,0 +1,21 @@
+package com.example.firstapp.database.repository
+
+import androidx.annotation.WorkerThread
+import com.example.firstapp.database.dao.MsgDao
+import com.example.firstapp.database.entity.Msg
+
+
+class MsgRepository(private val msgDao: MsgDao) {
+
+    @WorkerThread
+    fun insert(msg: Msg): Long = msgDao.insert(msg)
+
+    @WorkerThread
+    fun delete(id: Long) = msgDao.delete(id)
+
+    fun deleteAll() = msgDao.deleteAll()
+
+    @WorkerThread
+    fun deleteTimeAgo(time: Long) = msgDao.deleteTimeAgo(time)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/entity/Item.kt b/app/src/main/java/com/example/firstapp/entity/Item.kt
new file mode 100644
index 0000000..4f415da
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/entity/Item.kt
@@ -0,0 +1,7 @@
+package com.example.firstapp.entity
+
+data class Item(
+    val id: Int,
+    val title: String,
+    val description: String
+)
diff --git a/app/src/main/java/com/example/firstapp/entity/Rule.kt b/app/src/main/java/com/example/firstapp/entity/Rule.kt
new file mode 100644
index 0000000..3bb161e
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/entity/Rule.kt
@@ -0,0 +1,17 @@
+package com.example.firstapp.entity
+
+data class Rule(var type: String,var content: String ,var reg: String) {
+
+    // 在 Rule 类中定义提取方法
+    fun extractCodeFromMessage(message: String): String? {
+        // 如果 message 包含 content
+        if (message.contains(content)) {
+            // 使用 reg 作为正则表达式进行匹配
+            val regex = reg.toRegex()
+            val matchResult = regex.find(message)
+            return matchResult?.value  // 如果找到匹配的内容,则返回
+        }
+        return null  // 如果不匹配,返回 null
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/entity/SmsInfo.kt b/app/src/main/java/com/example/firstapp/entity/SmsInfo.kt
new file mode 100644
index 0000000..e2c0ef0
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/entity/SmsInfo.kt
@@ -0,0 +1,37 @@
+package com.example.firstapp.entity
+
+import com.example.firstapp.R
+import com.google.gson.annotations.SerializedName
+
+import java.io.Serializable
+
+data class SmsInfo(
+    // 联系人姓名
+    var name: String = "",
+    // 联系人号码
+    var number: String = "",
+    // 短信内容
+    var content: String = "",
+    // 短信时间
+    var date: Long = 0L,
+    // 短信类型: 1=接收, 2=发送
+    var type: Int = 1,
+    // 卡槽ID: 0=Sim1, 1=Sim2, -1=获取失败
+    @SerializedName("sim_id")
+    var simId: Int = -1,
+    // 卡槽主键
+    @SerializedName("sub_id")
+    var subId: Int = 0,
+) : Serializable {
+
+    val typeImageId: Int = R.drawable.ic_sms
+
+    val simImageId: Int
+        get() {
+            return when (simId) {
+                0 -> R.drawable.ic_sim1
+                1 -> R.drawable.ic_sim2
+                else -> R.drawable.ic_sim
+            }
+        }
+}
diff --git a/app/src/main/java/com/example/firstapp/receiver/CactusReceiver.kt b/app/src/main/java/com/example/firstapp/receiver/CactusReceiver.kt
new file mode 100644
index 0000000..42aeaec
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/receiver/CactusReceiver.kt
@@ -0,0 +1,33 @@
+package com.example.firstapp.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.example.firstapp.App
+import com.example.firstapp.utils.Log
+import com.gyf.cactus.Cactus
+
+//接收Cactus广播
+class CactusReceiver : BroadcastReceiver() {
+    override fun onReceive(context: Context, intent: Intent) {
+        intent.action?.apply {
+            when (this) {
+                Cactus.CACTUS_WORK -> {
+                    Log.d(
+                        App.TAG,
+                        this + "--" + intent.getIntExtra(Cactus.CACTUS_TIMES, 0)
+                    )
+                }
+                Cactus.CACTUS_STOP -> {
+                    Log.d(App.TAG, this)
+                }
+                Cactus.CACTUS_BACKGROUND -> {
+                    Log.d(App.TAG, this)
+                }
+                Cactus.CACTUS_FOREGROUND -> {
+                    Log.d(App.TAG, this)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt b/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
new file mode 100644
index 0000000..1c2a82f
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt
@@ -0,0 +1,66 @@
+package com.example.firstapp.receiver
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.provider.Telephony
+import android.telephony.SmsMessage
+import android.util.Log
+import com.example.firstapp.core.Core
+import com.example.firstapp.database.entity.Code
+import com.example.firstapp.database.entity.Msg
+import com.example.firstapp.entity.Rule
+
+
+class SmsReceiver : BroadcastReceiver() {
+
+
+    override fun onReceive(context: Context, intent: Intent) {
+        // 检查广播的 Action 是否为短信接收
+        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION == intent.action) {
+            // 获取短信内容
+            val bundle: Bundle? = intent.extras
+            bundle?.let {
+                val pdus = it.get("pdus") as Array<*>
+                val messages = arrayOfNulls<SmsMessage>(pdus.size)
+                val messageBody = StringBuilder()
+
+                for (i in pdus.indices) {
+                    messages[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
+                    messageBody.append(messages[i]?.messageBody)
+                }
+
+                // 输出短信内容到控制台
+                Log.d("SmsReceiver", "Received SMS: ${messageBody.toString()}")
+                val msg = Msg(0, "1111", "111111", messageBody.toString(),1, "111", 1, 1)
+                val msgId = Core.msg.insert(msg)
+                Log.d("SmsReceiver", "Received SMS msgId: ${msgId}")
+
+                // 这里我要写个数组,并创建个对象存放一些内容,如这个对象的属性有匹配内容,正则表达式,并循环遍历
+                val ruleList = listOf(
+                    Rule("快递","京东","\\d{6}"),
+                    Rule("快递","菜鸟驿站","\\d{1,2}-\\d{1,2}-\\d{4}")
+                )
+
+                // kotlin 怎么创建一个类
+                for (rule in ruleList) {
+                    val code = rule.extractCodeFromMessage(messageBody.toString())
+
+                    if (code!==null) {
+                        Log.d("SmsReceiver", "Received SMS code: ${code}")
+                        // 封装成一个Code对象,并保存在数据库中
+                        val code = Code(0, rule.type,1, rule.content,1, 1,  msgId, code)
+                        Core.code.insert(code)
+                        // 发送广播通知数据已更新
+                        val updateIntent = Intent("com.example.firstapp.DATA_UPDATED")
+                        context.sendBroadcast(updateIntent)
+                    }else{
+                        Log.d("SmsReceiver", "Received SMS code: 没有匹配到内容")
+                    }
+                }
+
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/com/example/firstapp/service/BluetoothScanService.kt b/app/src/main/java/com/example/firstapp/service/BluetoothScanService.kt
new file mode 100644
index 0000000..ee4f1ed
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/service/BluetoothScanService.kt
@@ -0,0 +1,73 @@
+package com.example.firstapp.service
+
+import android.Manifest
+import android.app.Service
+import android.bluetooth.BluetoothAdapter
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.IBinder
+import androidx.core.app.ActivityCompat
+import com.example.firstapp.utils.ACTION_RESTART
+import com.example.firstapp.utils.ACTION_START
+import com.example.firstapp.utils.ACTION_STOP
+import com.example.firstapp.utils.Log
+//import com.example.firstapp.utils.task.TaskUtils
+
+@Suppress("PrivatePropertyName", "DEPRECATION")
+class BluetoothScanService : Service() {
+
+    private val TAG: String = BluetoothScanService::class.java.simpleName
+    private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
+
+    companion object {
+        var isRunning = false
+    }
+
+    override fun onBind(p0: Intent?): IBinder? {
+        return null
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        if (intent == null) return START_NOT_STICKY
+        Log.i(TAG, "onStartCommand: ${intent.action}")
+
+        when (intent.action) {
+            ACTION_START -> startDiscovery()
+            ACTION_STOP -> stopDiscovery()
+            ACTION_RESTART -> {
+                stopDiscovery()
+                startDiscovery()
+            }
+        }
+        return START_NOT_STICKY
+    }
+
+    // 开始扫描蓝牙设备
+    private fun startDiscovery() {
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+            return
+        }
+        if (isRunning) return
+        // 清空已发现设备
+//        TaskUtils.discoveredDevices = mutableMapOf()
+        bluetoothAdapter?.startDiscovery()
+        isRunning = true
+    }
+
+    // 停止蓝牙扫描
+    private fun stopDiscovery() {
+        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+            return
+        }
+        if (!isRunning) return
+        bluetoothAdapter?.cancelDiscovery()
+        isRunning = false
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        isRunning = false
+        stopDiscovery()
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/service/HttpServerService.kt b/app/src/main/java/com/example/firstapp/service/HttpServerService.kt
new file mode 100644
index 0000000..29fa68e
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/service/HttpServerService.kt
@@ -0,0 +1,62 @@
+package com.example.firstapp.service
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import com.example.firstapp.utils.Log
+import com.example.firstapp.utils.HTTP_SERVER_PORT
+import com.example.firstapp.utils.HTTP_SERVER_TIME_OUT
+import com.example.firstapp.utils.SettingUtils
+import com.yanzhenjie.andserver.AndServer
+import com.yanzhenjie.andserver.Server
+import java.util.concurrent.TimeUnit
+
+@Suppress("PrivatePropertyName")
+class HttpServerService : Service(), Server.ServerListener {
+
+    private val TAG: String = HttpServerService::class.java.simpleName
+    private val server by lazy {
+        AndServer.webServer(this).port(HTTP_SERVER_PORT).listener(this).timeout(HTTP_SERVER_TIME_OUT, TimeUnit.SECONDS).build()
+    }
+
+    override fun onBind(p0: Intent?): IBinder? {
+        return null
+    }
+
+    override fun onCreate() {
+        super.onCreate()
+
+        //纯客户端模式
+        if (SettingUtils.enablePureClientMode) return
+
+        Log.i(TAG, "onCreate: ")
+        server.startup()
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        Log.i(TAG, "onStartCommand: ")
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        //纯客户端模式
+        if (SettingUtils.enablePureClientMode) return
+
+        Log.i(TAG, "onDestroy: ")
+        server.shutdown()
+    }
+
+    override fun onException(e: Exception?) {
+        Log.i(TAG, "onException: ")
+    }
+
+    override fun onStarted() {
+        Log.i(TAG, "onStarted: ")
+    }
+
+    override fun onStopped() {
+        Log.i(TAG, "onStopped: ")
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
new file mode 100644
index 0000000..0be390f
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
@@ -0,0 +1,42 @@
+package com.example.firstapp.ui.dashboard
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.example.firstapp.databinding.FragmentDashboardBinding
+
+class DashboardFragment : Fragment() {
+
+    private var _binding: FragmentDashboardBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val dashboardViewModel =
+            ViewModelProvider(this).get(DashboardViewModel::class.java)
+
+        _binding = FragmentDashboardBinding.inflate(inflater, container, false)
+        val root: View = binding.root
+
+        val textView: TextView = binding.textDashboard
+        dashboardViewModel.text.observe(viewLifecycleOwner) {
+            textView.text = it
+        }
+        return root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
new file mode 100644
index 0000000..e47a4a6
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
@@ -0,0 +1,13 @@
+package com.example.firstapp.ui.dashboard
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class DashboardViewModel : ViewModel() {
+
+    private val _text = MutableLiveData<String>().apply {
+        value = "This is dashboard Fragment"
+    }
+    val text: LiveData<String> = _text
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt
new file mode 100644
index 0000000..bf4b7ec
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt
@@ -0,0 +1,71 @@
+package com.example.firstapp.ui.home
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.example.firstapp.R
+import com.example.firstapp.adapter.MyAdapter
+import com.example.firstapp.core.Core
+import com.example.firstapp.databinding.FragmentHomeBinding
+
+class HomeFragment : Fragment() {
+
+    private var _binding: FragmentHomeBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
+    private lateinit var homeViewModel: HomeViewModel
+    private lateinit var adapter: MyAdapter
+
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        homeViewModel =
+            ViewModelProvider(this).get(HomeViewModel::class.java)
+
+        _binding = FragmentHomeBinding.inflate(inflater, container, false)
+        val root: View = binding.root
+
+//        val textView: TextView = binding.textHome
+//        homeViewModel.text.observe(viewLifecycleOwner) {
+//            textView.text = it
+//        }
+
+
+        // 初始化适配器
+        adapter = MyAdapter()
+
+        // 获取数据
+//        val codeList = Core.code.getAllDesc()
+
+        // 使用 binding 来访问 RecyclerView
+        val recyclerView: RecyclerView = binding.recyclerView
+        recyclerView.layoutManager = LinearLayoutManager(requireContext()) // 使用 requireContext() 获取上下文
+        recyclerView.adapter = adapter
+
+        // 观察 LiveData,当数据发生变化时,更新 RecyclerView 的内容
+        homeViewModel.codeList.observe(viewLifecycleOwner) { codeList ->
+            adapter.submitList(codeList) // 更新 RecyclerView 的数据
+            // 滚动到顶部
+            recyclerView.scrollToPosition(0)
+        }
+
+        return root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
new file mode 100644
index 0000000..630b6af
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -0,0 +1,36 @@
+package com.example.firstapp.ui.home
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.example.firstapp.core.Core
+import com.example.firstapp.database.entity.Code
+
+class HomeViewModel : ViewModel() {
+
+    private val _text = MutableLiveData<String>().apply {
+        value = "短信主页面"
+    }
+    val text: LiveData<String> = _text
+
+    private val _codeList = MutableLiveData<List<Code>>()
+
+    val codeList: LiveData<List<Code>> get() = _codeList
+
+    init {
+        // 初始化时加载数据
+        loadData()
+    }
+
+    // 加载数据的方法
+    fun loadData() {
+        // 获取数据,并更新 LiveData
+        _codeList.value = Core.code.getAllDesc() // 假设这是获取最新的 data 的方法
+    }
+
+    // 如果需要手动更新数据,可以调用这个方法
+    fun updateData() {
+        _codeList.value = Core.code.getAllDesc() // 重新获取并更新数据
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt b/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
new file mode 100644
index 0000000..3f7139f
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt
@@ -0,0 +1,42 @@
+package com.example.firstapp.ui.notifications
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.example.firstapp.databinding.FragmentNotificationsBinding
+
+class NotificationsFragment : Fragment() {
+
+    private var _binding: FragmentNotificationsBinding? = null
+
+    // This property is only valid between onCreateView and
+    // onDestroyView.
+    private val binding get() = _binding!!
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val notificationsViewModel =
+            ViewModelProvider(this).get(NotificationsViewModel::class.java)
+
+        _binding = FragmentNotificationsBinding.inflate(inflater, container, false)
+        val root: View = binding.root
+
+        val textView: TextView = binding.textNotifications
+        notificationsViewModel.text.observe(viewLifecycleOwner) {
+            textView.text = it
+        }
+        return root
+    }
+
+    override fun onDestroyView() {
+        super.onDestroyView()
+        _binding = null
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsViewModel.kt b/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsViewModel.kt
new file mode 100644
index 0000000..08127d7
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/ui/notifications/NotificationsViewModel.kt
@@ -0,0 +1,13 @@
+package com.example.firstapp.ui.notifications
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+
+class NotificationsViewModel : ViewModel() {
+
+    private val _text = MutableLiveData<String>().apply {
+        value = "This is notifications Fragment"
+    }
+    val text: LiveData<String> = _text
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/AppUtils.kt b/app/src/main/java/com/example/firstapp/utils/AppUtils.kt
new file mode 100644
index 0000000..29bd5ac
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/AppUtils.kt
@@ -0,0 +1,104 @@
+package com.example.firstapp.utils
+
+import android.annotation.SuppressLint
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import com.example.firstapp.App
+
+data class AppInfo(
+    val name: String,
+    val icon: Drawable,
+    val packageName: String,
+    val packagePath: String,
+    val versionName: String,
+    val versionCode: Int,
+    val isSystem: Boolean,
+    val uid: Int
+)
+
+@SuppressLint("StaticFieldLeak")
+@Suppress("DEPRECATION")
+object AppUtils {
+
+    fun getAppsInfo(): List<AppInfo> {
+        val packageManager = App.context.packageManager ?: return emptyList()
+        val appsInfo = mutableListOf<AppInfo>()
+
+        val apps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
+        for (app in apps) {
+            try {
+                val packageInfo = packageManager.getPackageInfo(app.packageName, 0)
+                val appInfo = AppInfo(
+                    app.loadLabel(packageManager).toString(),
+                    app.loadIcon(packageManager),
+                    app.packageName,
+                    app.sourceDir,
+                    packageInfo?.versionName ?: "Unknown",
+                    packageInfo?.versionCode ?: 0,
+                    (app.flags and ApplicationInfo.FLAG_SYSTEM) != 0,
+                    app.uid
+                )
+                appsInfo.add(appInfo)
+            } catch (e: PackageManager.NameNotFoundException) {
+                e.printStackTrace()
+                Log.e("AppUtils", "getAppsInfo: ${e.message}")
+            }
+        }
+
+        return appsInfo
+    }
+
+    fun getAppVersionCode(): Int {
+        return getAppVersionCode(App.context.packageName)
+    }
+
+    private fun getAppVersionCode(packageName: String?): Int {
+        if (packageName.isNullOrBlank()) {
+            return -1
+        }
+        return try {
+            val pm: PackageManager = App.context.packageManager
+            val pi: PackageInfo = pm.getPackageInfo(packageName, 0)
+            pi.versionCode
+        } catch (e: PackageManager.NameNotFoundException) {
+            e.printStackTrace()
+            Log.e("AppUtils", "getAppVersionCode: ${e.message}")
+            -1
+        }
+    }
+
+    fun getAppPackageName(): String {
+        return App.context.packageName
+    }
+
+    fun getAppVersionName(): String {
+        return getAppVersionName(App.context.packageName)
+    }
+
+    private fun getAppVersionName(packageName: String): String {
+        if (packageName.isBlank()) {
+            return ""
+        }
+        return try {
+            val pm: PackageManager = App.context.packageManager
+            val pi: PackageInfo = pm.getPackageInfo(packageName, 0)
+            pi.versionName ?: ""
+        } catch (e: PackageManager.NameNotFoundException) {
+            e.printStackTrace()
+            Log.e("AppUtils", "getAppVersionName: ${e.message}")
+            ""
+        }
+    }
+
+    /*fun openApp(packageName: String) {
+        val packageManager = App.context.packageManager
+        val intent = packageManager.getLaunchIntentForPackage(packageName)
+        if (intent != null) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            App.context.startActivity(intent)
+        }
+    }*/
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/Base64.kt b/app/src/main/java/com/example/firstapp/utils/Base64.kt
new file mode 100644
index 0000000..1744f81
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/Base64.kt
@@ -0,0 +1,88 @@
+package com.example.firstapp.utils
+
+import java.io.UnsupportedEncodingException
+
+/**
+ * Base64编码解码
+ */
+object Base64 {
+
+    private val base64EncodeChars = charArrayOf('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/')
+
+    private val base64DecodeChars = byteArrayOf(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1)
+
+    fun encode(data: ByteArray): String {
+        val sb = StringBuffer()
+        val len = data.size
+        var i = 0
+        var b1: Int
+        var b2: Int
+        var b3: Int
+        while (i < len) {
+            b1 = ((data[i++]).toInt() and 0xff)
+            if (i == len) {
+                sb.append(base64EncodeChars[b1.ushr(2)])
+                sb.append(base64EncodeChars[b1 and 0x3 shl 4])
+                sb.append("==")
+                break
+            }
+            b2 = (data[i++]).toInt() and 0xff
+            if (i == len) {
+                sb.append(base64EncodeChars[b1.ushr(2)])
+                sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)])
+                sb.append(base64EncodeChars[b2 and 0x0f shl 2])
+                sb.append("=")
+                break
+            }
+            b3 = (data[i++]).toInt() and 0xff
+            sb.append(base64EncodeChars[b1.ushr(2)])
+            sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0).ushr(4)])
+            sb.append(base64EncodeChars[b2 and 0x0f shl 2 or (b3 and 0xc0).ushr(6)])
+            sb.append(base64EncodeChars[b3 and 0x3f])
+        }
+        return sb.toString()
+    }
+
+    @Throws(UnsupportedEncodingException::class)
+    fun decode(str: String): ByteArray {
+        val sb = StringBuffer()
+        val data = str.toByteArray(charset("US-ASCII"))
+        val len = data.size
+        var i = 0
+        var b1: Int
+        var b2: Int
+        var b3: Int
+        var b4: Int
+        while (i < len) {
+            /* b1 */
+            do {
+                b1 = base64DecodeChars[(data[i++]).toInt()].toInt()
+            } while (i < len && b1 == -1)
+            if (b1 == -1) break
+            /* b2 */
+            do {
+                b2 = base64DecodeChars[(data[i++]).toInt()].toInt()
+            } while (i < len && b2 == -1)
+            if (b2 == -1) break
+            sb.append((b1 shl 2 or (b2 and 0x30).ushr(4)).toChar())
+            /* b3 */
+            do {
+                b3 = data[i++].toInt()
+                if (b3 == 61) return sb.toString().toByteArray(charset("ISO-8859-1"))
+                b3 = base64DecodeChars[b3].toInt()
+            } while (i < len && b3 == -1)
+            if (b3 == -1) break
+            sb.append((b2 and 0x0f shl 4 or (b3 and 0x3c).ushr(2)).toChar())
+            /* b4 */
+            do {
+                b4 = data[i++].toInt()
+                if (b4 == 61) return sb.toString().toByteArray(charset("ISO-8859-1"))
+                b4 = base64DecodeChars[b4].toInt()
+            } while (i < len && b4 == -1)
+            if (b4 == -1) break
+            sb.append((b3 and 0x03 shl 6 or b4).toChar())
+        }
+        return sb.toString().toByteArray(charset("ISO-8859-1"))
+    }
+
+}
diff --git a/app/src/main/java/com/example/firstapp/utils/BluetoothUtils.kt b/app/src/main/java/com/example/firstapp/utils/BluetoothUtils.kt
new file mode 100644
index 0000000..aacbf19
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/BluetoothUtils.kt
@@ -0,0 +1,38 @@
+package com.example.firstapp.utils
+
+import android.bluetooth.BluetoothAdapter
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.core.content.ContextCompat
+
+@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
+object BluetoothUtils {
+
+    /**
+     * 检查应用是否具有蓝牙权限
+     */
+    fun hasBluetoothPermission(context: Context): Boolean {
+        return ContextCompat.checkSelfPermission(context, android.Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED
+                && ContextCompat.checkSelfPermission(context, android.Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED
+    }
+
+    /**
+     * 检查蓝牙是否已启用
+     */
+    fun isBluetoothEnabled(): Boolean {
+        val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+        return bluetoothAdapter != null && bluetoothAdapter.isEnabled
+    }
+
+    /**
+     * 检查设备是否支持蓝牙功能
+     */
+    fun hasBluetoothCapability(context: Context): Boolean {
+        if (!hasBluetoothPermission(context)) {
+            Log.e("BluetoothUtils", "hasBluetoothCapability: no bluetooth permission")
+            return false
+        }
+
+        return context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)
+    }
+}
diff --git a/app/src/main/java/com/example/firstapp/utils/CacheUtils.kt b/app/src/main/java/com/example/firstapp/utils/CacheUtils.kt
new file mode 100644
index 0000000..4a6b7ae
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/CacheUtils.kt
@@ -0,0 +1,108 @@
+package com.example.firstapp.utils
+
+import android.content.Context
+import android.os.Environment
+import java.io.File
+import java.math.BigDecimal
+
+@Suppress("DEPRECATION")
+class CacheUtils private constructor() {
+    companion object {
+        /**
+         * 获取缓存大小
+         *
+         * @param context 上下文
+         * @return 缓存大小
+         */
+        fun getTotalCacheSize(context: Context): String {
+            return try {
+                var cacheSize = getFolderSize(context.cacheDir)
+                if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
+                    cacheSize += getFolderSize(context.externalCacheDir)
+                }
+                getFormatSize(cacheSize.toDouble())
+            } catch (e: Exception) {
+                e.printStackTrace()
+                Log.e("CacheUtils", "getTotalCacheSize: ${e.message}")
+                "0KB"
+            }
+        }
+
+        /***
+         * 清理所有缓存
+         * @param context 上下文
+         */
+        fun clearAllCache(context: Context) {
+            deleteDir(context.cacheDir)
+            if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
+                deleteDir(context.externalCacheDir)
+            }
+        }
+
+        private fun deleteDir(dir: File?): Boolean {
+            if (dir != null && dir.isDirectory) {
+                val children = dir.list()!!
+                for (child in children) {
+                    val success = deleteDir(File(dir, child))
+                    if (!success) {
+                        return false
+                    }
+                }
+            }
+            assert(dir != null)
+            return dir!!.delete()
+        }
+
+        // 获取文件
+        //Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据
+        //Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
+        private fun getFolderSize(file: File?): Long {
+            var size: Long = 0
+            try {
+                val fileList = file!!.listFiles()!!
+                for (value in fileList) {
+                    // 如果下面还有文件
+                    size = if (value.isDirectory) {
+                        size + getFolderSize(value)
+                    } else {
+                        size + value.length()
+                    }
+                }
+            } catch (e: Exception) {
+                e.printStackTrace()
+                Log.e("CacheUtils", "getFolderSize: ${e.message}")
+            }
+            return size
+        }
+
+        /**
+         * 格式化单位
+         *
+         * @param size 文件大小
+         * @return 结果
+         */
+        private fun getFormatSize(size: Double): String {
+            val kiloByte = size / 1024
+            if (kiloByte < 1) {
+                return "0KB"
+            }
+            val megaByte = kiloByte / 1024
+            if (megaByte < 1) {
+                val result1 = BigDecimal(kiloByte.toString())
+                return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
+            }
+            val gigaByte = megaByte / 1024
+            if (gigaByte < 1) {
+                val result2 = BigDecimal(megaByte.toString())
+                return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
+            }
+            val teraBytes = gigaByte / 1024
+            if (teraBytes < 1) {
+                val result3 = BigDecimal(gigaByte.toString())
+                return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
+            }
+            val result4 = BigDecimal(teraBytes)
+            return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/CactusSave.kt b/app/src/main/java/com/example/firstapp/utils/CactusSave.kt
new file mode 100644
index 0000000..9c49615
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/CactusSave.kt
@@ -0,0 +1,15 @@
+package com.example.firstapp.utils
+
+object CactusSave {
+    //Cactus存活时间
+    var timer: Long by SharedPreference(CACTUS_TIMER, 0L)
+
+    //Cactus上次存活时间
+    var lastTimer: Long by SharedPreference(CACTUS_LAST_TIMER, 0L)
+
+    //Cactus运行时间
+    var date: String by SharedPreference(CACTUS_DATE, "0000-01-01 00:00:00")
+
+    //Cactus结束时间
+    var endDate: String by SharedPreference(CACTUS_END_DATE, "0000-01-01 00:00:00")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/Constants.kt b/app/src/main/java/com/example/firstapp/utils/Constants.kt
new file mode 100644
index 0000000..354972b
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/Constants.kt
@@ -0,0 +1,318 @@
+package com.example.firstapp.utils
+
+object Worker {
+    const val SEND_MSG_INFO = "send_msg_info"
+    const val UPDATE_LOGS = "update_logs"
+    const val RULE = "rule"
+    const val SENDER_INDEX = "sender_index"
+    const val MSG_ID = "msg_id"
+}
+
+object TaskWorker {
+    const val TASK_ID = "task_id"
+    const val TASK = "task"
+    const val TASK_CONDITIONS = "task_conditions"
+    const val TASK_ACTIONS = "task_actions"
+    const val CONDITION_TYPE = "condition_type"
+    const val MSG = "msg"
+    const val MSG_INFO = "msg_info"
+    const val ACTION = "action"
+}
+
+//服务相关
+const val ACTION_START = "START"
+const val ACTION_STOP = "STOP"
+const val ACTION_RESTART = "RESTART"
+const val ACTION_STOP_ALARM = "STOP_ALARM"
+const val ACTION_UPDATE_NOTIFICATION = "UPDATE_NOTIFICATION"
+const val EXTRA_UPDATE_NOTIFICATION = "EXTRA_UPDATE_NOTIFICATION"
+
+//初始化相关
+const val AUTO_CHECK_UPDATE = "auto_check_update"
+const val JOIN_PREVIEW_PROGRAM = "join_preview_program"
+const val IS_AGREE_PRIVACY_KEY = "is_agree_privacy_key"
+
+//数据库
+const val DATABASE_NAME = "sms_first_app.db"
+const val PACKAGE_NAME = "com.example.firstapp"
+
+//通用设置
+const val SP_ENABLE_SMS = "enable_sms"
+
+const val SP_ENABLE_PHONE = "enable_phone"
+const val SP_ENABLE_CALL_TYPE_1 = "enable_call_type_1"
+const val SP_ENABLE_CALL_TYPE_2 = "enable_call_type_2"
+const val SP_ENABLE_CALL_TYPE_3 = "enable_call_type_3"
+const val SP_ENABLE_CALL_TYPE_4 = "enable_call_type_4"
+const val SP_ENABLE_CALL_TYPE_5 = "enable_call_type_5"
+const val SP_ENABLE_CALL_TYPE_6 = "enable_call_type_6"
+
+const val SP_ENABLE_APP_NOTIFY = "enable_app_notify"
+const val SP_ENABLE_CANCEL_APP_NOTIFY = "enable_cancel_app_notify"
+const val SP_CANCEL_EXTRA_APP_NOTIFY = "cancel_extra_app_notify_list"
+const val SP_ENABLE_NOT_USER_PRESENT = "enable_not_user_present"
+
+const val SP_ENABLE_SMS_COMMAND = "enable_sms_command"
+const val SP_SMS_COMMAND_SAFE_PHONE = "sms_command_safe_phone"
+
+const val ENABLE_LOAD_APP_LIST = "enable_load_app_list"
+const val ENABLE_LOAD_USER_APP_LIST = "enable_load_user_app_list"
+const val ENABLE_LOAD_SYSTEM_APP_LIST = "enable_load_system_app_list"
+
+const val SP_DUPLICATE_MESSAGES_LIMITS = "duplicate_messages_limits"
+const val SP_SILENT_PERIOD_START = "silent_period_start"
+const val SP_SILENT_PERIOD_END = "silent_period_end"
+const val SP_ENABLE_SILENT_PERIOD_LOGS = "enable_silent_period_logs"
+
+const val SP_ENABLE_EXCLUDE_FROM_RECENTS = "enable_exclude_from_recents"
+const val SP_ENABLE_PLAY_SILENCE_MUSIC = "enable_play_silence_music"
+const val SP_ENABLE_ONE_PIXEL_ACTIVITY = "enable_one_pixel_activity"
+
+const val SP_REQUEST_RETRY_TIMES = "request_retry_times"
+const val SP_REQUEST_DELAY_TIME = "request_delay_time"
+const val SP_REQUEST_TIMEOUT = "request_timeout"
+
+const val SP_NOTIFY_CONTENT = "notify_content"
+const val SP_EXTRA_DEVICE_MARK = "extra_device_mark"
+const val SP_SUBID_SIM1 = "subid_sim1"
+const val SP_SUBID_SIM2 = "subid_sim2"
+const val SP_EXTRA_SIM1 = "extra_sim1"
+const val SP_EXTRA_SIM2 = "extra_sim2"
+const val SP_ENABLE_SMS_TEMPLATE = "enable_sms_template"
+const val SP_SMS_TEMPLATE = "sms_template"
+
+const val SP_PURE_CLIENT_MODE = "enable_pure_client_mode"
+const val SP_PURE_TASK_MODE = "enable_pure_task_mode"
+const val SP_DEBUG_MODE = "enable_debug_mode"
+
+//const val SP_IS_FLOW_SYSTEM_LANGUAGE = "is_flow_system_language"
+const val SP_LOCATION = "enable_location"
+const val SP_LOCATION_ACCURACY = "location_accuracy"
+const val SP_LOCATION_POWER_REQUIREMENT = "location_power_requirement"
+const val SP_LOCATION_MIN_INTERVAL = "location_min_interval_time"
+const val SP_LOCATION_MIN_DISTANCE = "location_min_distance"
+
+const val SP_BLUETOOTH = "enable_bluetooth"
+const val SP_BLUETOOTH_SCAN_INTERVAL = "bluetooth_scan_interval"
+const val SP_BLUETOOTH_IGNORE_ANONYMOUS = "bluetooth_ignore_anonymous"
+
+const val SP_ENABLE_CACTUS = "enable_cactus"
+const val CACTUS_TIMER = "cactus_timer"
+const val CACTUS_LAST_TIMER = "cactus_last_timer"
+const val CACTUS_DATE = "cactus_date"
+const val CACTUS_END_DATE = "cactus_end_date"
+
+//规则相关
+const val STATUS_ON = 1
+const val STATUS_OFF = 0
+const val FILED_TRANSPOND_ALL = "transpond_all"
+const val FILED_PHONE_NUM = "phone_num"
+const val FILED_CALL_TYPE = "call_type"
+const val FILED_PACKAGE_NAME = "package_name"
+const val FILED_UID = "uid"
+const val FILED_MSG_CONTENT = "msg_content"
+const val FILED_INFORM_CONTENT = "inform_content"
+const val FILED_MULTI_MATCH = "multi_match"
+const val CHECK_IS = "is"
+const val CHECK_CONTAIN = "contain"
+const val CHECK_NOT_CONTAIN = "notcontain"
+const val CHECK_START_WITH = "startwith"
+const val CHECK_END_WITH = "endwith"
+const val CHECK_NOT_IS = "notis"
+const val CHECK_REGEX = "regex"
+const val CHECK_SIM_SLOT_ALL = "ALL"
+const val CHECK_SIM_SLOT_1 = "SIM1"
+const val CHECK_SIM_SLOT_2 = "SIM2"
+
+//发送通道执行逻辑:ALL=全部执行, UntilFail=失败即终止, UntilSuccess=成功即终止
+const val SENDER_LOGIC_ALL = "ALL"
+const val SENDER_LOGIC_UNTIL_FAIL = "UntilFail"
+const val SENDER_LOGIC_UNTIL_SUCCESS = "UntilSuccess"
+const val SENDER_LOGIC_RETRY = "Retry"
+
+//发送通道
+const val TYPE_DINGTALK_GROUP_ROBOT = 0
+const val TYPE_EMAIL = 1
+const val TYPE_BARK = 2
+const val TYPE_WEBHOOK = 3
+const val TYPE_WEWORK_ROBOT = 4
+const val TYPE_WEWORK_AGENT = 5
+const val TYPE_SERVERCHAN = 6
+const val TYPE_TELEGRAM = 7
+const val TYPE_SMS = 8
+const val TYPE_FEISHU = 9
+const val TYPE_PUSHPLUS = 10
+const val TYPE_GOTIFY = 11
+const val TYPE_DINGTALK_INNER_ROBOT = 12
+const val TYPE_FEISHU_APP = 13
+const val TYPE_URL_SCHEME = 14
+const val TYPE_SOCKET = 15
+
+//前台服务
+const val FRONT_NOTIFY_ID = 0x1010
+const val FRONT_CHANNEL_ID = "com.example.firstapp"
+const val FRONT_CHANNEL_NAME = "SmsForwarder Foreground Service"
+
+//Frp内网穿透
+const val FRPC_LIB_DOWNLOAD_URL = "https://xupdate.ppps.cn/uploads/%s/%s/libgojni.so"
+const val FRPC_LIB_VERSION = "0.57.0"
+const val EVENT_FRPC_UPDATE_CONFIG = "EVENT_FRPC_UPDATE_CONFIG"
+const val EVENT_FRPC_DELETE_CONFIG = "EVENT_FRPC_DELETE_CONFIG"
+const val EVENT_FRPC_RUNNING_ERROR = "EVENT_FRPC_RUNNING_ERROR"
+const val EVENT_FRPC_RUNNING_SUCCESS = "EVENT_FRPC_RUNNING_SUCCESS"
+const val INTENT_FRPC_EDIT_FILE = "INTENT_FRPC_EDIT_FILE"
+const val INTENT_FRPC_APPLY_FILE = "INTENT_FRPC_APPLY_FILE"
+
+//声音警报
+const val EVENT_ALARM_ACTION = "EVENT_ALARM_ACTION"
+
+//吐司监听
+const val EVENT_TOAST_SUCCESS = "key_toast_success"
+const val EVENT_TOAST_ERROR = "key_toast_error"
+const val EVENT_TOAST_INFO = "key_toast_info"
+const val EVENT_TOAST_WARNING = "key_toast_warning"
+
+const val KEY_SENDER_ID = "key_sender_id"
+const val KEY_SENDER_TYPE = "key_sender_type"
+const val KEY_SENDER_CLONE = "key_sender_clone"
+const val KEY_SENDER_TEST = "key_sender_test"
+
+const val KEY_RULE_ID = "key_rule_id"
+const val KEY_RULE_TYPE = "key_rule_type"
+const val KEY_RULE_CLONE = "key_rule_clone"
+const val KEY_DEFAULT_SELECTION = "key_default_selection"
+
+const val KEY_TASK_ID = "key_task_id"
+const val KEY_TASK_TYPE = "key_task_type"
+const val KEY_TASK_CLONE = "key_task_clone"
+
+const val EVENT_LOAD_APP_LIST = "EVENT_LOAD_APP_LIST"
+
+const val EVENT_KEY_SIM_SLOT = "EVENT_KEY_SIM_SLOT"
+const val EVENT_KEY_PHONE_NUMBERS = "EVENT_KEY_PHONE_NUMBERS"
+
+//在线升级&预览计划URL
+const val KEY_UPDATE_URL = "https://xupdate.ppps.cn/update/checkVersion"
+const val KEY_PREVIEW_URL = "https://xupdate.ppps.cn/preview/checkVersion"
+
+//HttpServer相关
+const val HTTP_SERVER_PORT = 5000
+const val HTTP_SERVER_TIME_OUT = 10
+const val HTTP_SUCCESS_CODE: Int = 200
+const val HTTP_FAILURE_CODE: Int = 500
+const val SP_ENABLE_SERVER_AUTORUN = "enable_server_autorun"
+const val SP_SERVER_SAFETY_MEASURES = "server_safety_measures"
+const val SP_SERVER_SIGN_KEY = "server_sign_key"
+const val SP_SERVER_TIME_TOLERANCE = "server_time_tolerance"
+const val SP_SERVER_SM4_KEY = "server_sm4_key"
+const val SP_SERVER_PUBLIC_KEY = "server_public_key"
+const val SP_SERVER_PRIVATE_KEY = "server_private_key"
+const val SP_SERVER_WEB_PATH = "server_web_path"
+const val SP_ENABLE_API_CLONE = "enable_api_clone"
+const val SP_ENABLE_API_SMS_SEND = "enable_api_sms_send"
+const val SP_ENABLE_API_SMS_QUERY = "enable_api_sms_query"
+const val SP_ENABLE_API_CALL_QUERY = "enable_api_call_query"
+const val SP_ENABLE_API_CONTACT_QUERY = "enable_api_contact_query"
+const val SP_ENABLE_API_CONTACT_ADD = "enable_api_contact_add"
+const val SP_ENABLE_API_BATTERY_QUERY = "enable_api_battery_query"
+const val SP_ENABLE_API_WOL = "enable_api_wol"
+const val SP_ENABLE_API_LOCATION = "enable_api_location"
+const val SP_API_LOCATION_CACHE = "api_location_cache"
+const val SP_WOL_HISTORY = "wol_history"
+const val SP_SERVER_ADDRESS = "server_address"
+const val SP_SERVER_HISTORY = "server_history"
+const val SP_SERVER_CONFIG = "server_config"
+const val SP_CLIENT_SAFETY_MEASURES = "client_safety_measures"
+const val SP_CLIENT_SIGN_KEY = "client_sign_key"
+
+
+//自动任务
+const val MAX_SETTING_NUM = 5 //最大条件/动作设置条数
+const val KEY_TEST_CONDITION = "key_test_condition"
+const val KEY_EVENT_DATA_CONDITION = "event_data_condition"
+const val KEY_EVENT_PARAMS_CONDITION = "event_params_condition"
+const val KEY_BACK_CODE_CONDITION = 1000
+const val KEY_BACK_DATA_CONDITION = "back_data_condition"
+const val KEY_BACK_DESCRIPTION_CONDITION = "back_description_condition"
+
+const val KEY_EVENT_DATA_ACTION = "event_data_action"
+const val KEY_BACK_CODE_ACTION = 2000
+const val KEY_BACK_DATA_ACTION = "back_data_action"
+const val KEY_BACK_DESCRIPTION_ACTION = "back_description_action"
+
+//注意:TASK_CONDITION_XXX 枚举值 等于 TASK_CONDITION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_CONDITION,不可改变
+const val TASK_CONDITION_CRON = 1000
+const val TASK_CONDITION_TO_ADDRESS = 1001
+const val TASK_CONDITION_LEAVE_ADDRESS = 1002
+const val TASK_CONDITION_NETWORK = 1003
+const val TASK_CONDITION_SIM = 1004
+const val TASK_CONDITION_BATTERY = 1005
+const val TASK_CONDITION_CHARGE = 1006
+const val TASK_CONDITION_LOCK_SCREEN = 1007
+const val TASK_CONDITION_SMS = 1008
+const val TASK_CONDITION_CALL = 1009
+const val TASK_CONDITION_APP = 1010
+const val TASK_CONDITION_BLUETOOTH = 1011
+
+//注意:TASK_ACTION_XXX 枚举值 等于 TASK_ACTION_FRAGMENT_LIST 索引加上 KEY_BACK_CODE_ACTION,不可改变
+const val TASK_ACTION_SENDSMS = 2000
+const val TASK_ACTION_NOTIFICATION = 2001
+const val TASK_ACTION_CLEANER = 2002
+const val TASK_ACTION_SETTINGS = 2003
+const val TASK_ACTION_FRPC = 2004
+const val TASK_ACTION_HTTPSERVER = 2005
+const val TASK_ACTION_RULE = 2006
+const val TASK_ACTION_SENDER = 2007
+const val TASK_ACTION_ALARM = 2008
+const val TASK_ACTION_RESEND = 2009
+const val TASK_ACTION_TASK = 2010
+
+const val SP_BATTERY_INFO = "battery_info"
+const val SP_BATTERY_STATUS = "battery_status"
+const val SP_BATTERY_LEVEL = "battery_level"
+const val SP_BATTERY_PCT = "battery_pct"
+const val SP_BATTERY_PLUGGED = "battery_plugged"
+
+const val SP_NETWORK_STATE = "network_state"
+const val SP_DATA_SIM_SLOT = "data_sim_slot"
+const val SP_WIFI_SSID = "wifi_ssid"
+const val SP_IPV4 = "ipv4"
+const val SP_IPV6 = "ipv6"
+const val SP_IP_LIST = "ip_list"
+const val SP_SIM_STATE = "sim_state"
+const val SP_LOCATION_INFO_OLD = "location_info_old"
+const val SP_LOCATION_INFO_NEW = "location_info_new"
+const val SP_LOCK_SCREEN_ACTION = "lock_screen_action"
+const val SP_CONNECTED_DEVICE = "connected_device"
+const val SP_DISCOVERED_DEVICES = "discovered_devices"
+const val SP_BLUETOOTH_STATE = "bluetooth_state"
+
+//SIM卡已准备就绪时,延迟5秒(给够搜索信号时间)才执行任务
+const val DELAY_TIME_AFTER_SIM_READY = 5000L
+
+//切换语言需要替换的自定义模板标签列表
+//val TAG_LANG = arrayOf("zh_CN", "zh_TW", "en")
+val TAG_LIST = arrayOf(
+    mapOf("zh_CN" to "{{来源号码}}", "zh_TW" to "{{來源號碼}}", "en" to "{{FROM}}"),
+    mapOf("zh_CN" to "{{短信内容}}", "zh_TW" to "{{簡訊內容}}", "en" to "{{SMS}}"),
+    mapOf("zh_CN" to "{{APP包名}}", "zh_TW" to "{{APP包名}}", "en" to "{{PACKAGE_NAME}}"),
+    mapOf("zh_CN" to "{{APP名称}}", "zh_TW" to "{{APP名稱}}", "en" to "{{APP_NAME}}"),
+    mapOf("zh_CN" to "{{通知内容}}", "zh_TW" to "{{通知內容}}", "en" to "{{MSG}}"),
+    mapOf("zh_CN" to "{{卡槽信息}}", "zh_TW" to "{{卡槽信息}}", "en" to "{{CARD_SLOT}}"),
+    mapOf("zh_CN" to "{{卡槽主键}}", "zh_TW" to "{{卡槽主鍵}}", "en" to "{{CARD_SUBID}}"),
+    mapOf("zh_CN" to "{{接收时间}}", "zh_TW" to "{{接收時間}}", "en" to "{{RECEIVE_TIME}}"),
+    mapOf("zh_CN" to "{{当前时间}}", "zh_TW" to "{{當前時間}}", "en" to "{{CURRENT_TIME}}"),
+    mapOf("zh_CN" to "{{设备名称}}", "zh_TW" to "{{設備名稱}}", "en" to "{{DEVICE_NAME}}"),
+    mapOf("zh_CN" to "{{当前应用版本号}}", "zh_TW" to "{{當前應用版本號}}", "en" to "{{APP_VERSION}}"),
+    mapOf("zh_CN" to "{{通知标题}}", "zh_TW" to "{{通知標題}}", "en" to "{{TITLE}}"),
+    mapOf("zh_CN" to "{{通知Scheme}}", "zh_TW" to "{{通知Scheme}}", "en" to "{{SCHEME}}"),
+    mapOf("zh_CN" to "{{通话类型}}", "zh_TW" to "{{通話類型}}", "en" to "{{CALL_TYPE}}"),
+    mapOf("zh_CN" to "{{定位信息}}", "zh_TW" to "{{定位信息}}", "en" to "{{LOCATION}}"),
+    mapOf("zh_CN" to "{{定位信息_经度}}", "zh_TW" to "{{定位信息_經度}}", "en" to "{{LOCATION_LONGITUDE}}"),
+    mapOf("zh_CN" to "{{定位信息_纬度}}", "zh_TW" to "{{定位信息_緯度}}", "en" to "{{LOCATION_LATITUDE}}"),
+    mapOf("zh_CN" to "{{定位信息_地址}}", "zh_TW" to "{{定位信息_地址}}", "en" to "{{LOCATION_ADDRESS}}"),
+    mapOf("zh_CN" to "{{电池电量}}", "zh_TW" to "{{電池電量}}", "en" to "{{BATTERY_PCT}}"),
+    mapOf("zh_CN" to "{{电池状态}}", "zh_TW" to "{{電池狀態}}", "en" to "{{BATTERY_STATUS}}"),
+    mapOf("zh_CN" to "{{充电方式}}", "zh_TW" to "{{充電方式}}", "en" to "{{BATTERY_PLUGGED}}"),
+    mapOf("zh_CN" to "{{电池信息}}", "zh_TW" to "{{電池信息}}", "en" to "{{BATTERY_INFO}}")
+)
diff --git a/app/src/main/java/com/example/firstapp/utils/DrawableUtils.kt b/app/src/main/java/com/example/firstapp/utils/DrawableUtils.kt
new file mode 100644
index 0000000..73e183f
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/DrawableUtils.kt
@@ -0,0 +1,59 @@
+package com.example.firstapp.utils
+
+import android.graphics.drawable.GradientDrawable
+
+/**
+ * @author xuexiang
+ * @since 2019/4/7 下午12:57
+ */
+@Suppress("unused")
+class DrawableUtils private constructor() {
+    companion object {
+        /**
+         * 矩形
+         */
+        fun createRectangleDrawable(color: Int, cornerRadius: Float): GradientDrawable {
+            val gradientDrawable = GradientDrawable()
+            gradientDrawable.shape = GradientDrawable.RECTANGLE
+            gradientDrawable.cornerRadius = cornerRadius
+            gradientDrawable.setColor(color)
+            return gradientDrawable
+        }
+
+        /**
+         * 矩形
+         */
+        fun createRectangleDrawable(colors: IntArray?, cornerRadius: Float): GradientDrawable {
+            val gradientDrawable = GradientDrawable()
+            gradientDrawable.shape = GradientDrawable.RECTANGLE
+            gradientDrawable.cornerRadius = cornerRadius
+            gradientDrawable.colors = colors
+            return gradientDrawable
+        }
+
+        /**
+         * 圆形
+         */
+        fun createOvalDrawable(color: Int): GradientDrawable {
+            val gradientDrawable = GradientDrawable()
+            gradientDrawable.shape = GradientDrawable.OVAL
+            gradientDrawable.setColor(color)
+            return gradientDrawable
+        }
+
+        /**
+         * 圆形
+         */
+        fun createOvalDrawable(colors: IntArray?): GradientDrawable {
+            val gradientDrawable = GradientDrawable()
+            gradientDrawable.shape = GradientDrawable.OVAL
+            gradientDrawable.colors = colors
+            return gradientDrawable
+        }
+    }
+
+    init {
+        throw UnsupportedOperationException("Can not be instantiated.")
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/FrpcUtils.kt b/app/src/main/java/com/example/firstapp/utils/FrpcUtils.kt
new file mode 100644
index 0000000..88d48de
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/FrpcUtils.kt
@@ -0,0 +1,51 @@
+package com.example.firstapp.utils
+
+import android.app.ActivityManager
+import android.content.Context
+import io.reactivex.Completable
+import io.reactivex.Observable
+import io.reactivex.ObservableEmitter
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.util.concurrent.TimeUnit
+
+@Suppress("DEPRECATION", "MemberVisibilityCanBePrivate")
+class FrpcUtils private constructor() {
+
+    companion object {
+
+        fun waitService(serviceName: String, context: Context): Completable {
+            return Completable.fromObservable(
+                Observable.interval(3, 1, TimeUnit.SECONDS)
+                    .takeUntil { isServiceRunning(serviceName, context) }
+            )
+        }
+
+        fun isServiceRunning(serviceName: String, context: Context): Boolean {
+            val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+            val runningServices = am.getRunningServices(Int.MAX_VALUE) //获取运行的服务,参数表示最多返回的数量
+            for (runningServiceInfo in runningServices) {
+                val className = runningServiceInfo.service.className
+                if (className == serviceName) {
+                    return true
+                }
+            }
+            return false
+        }
+
+        fun getStringFromRaw(context: Context, rawName: Int): Observable<String?> {
+            return Observable.create { emitter: ObservableEmitter<String?> ->
+                val reader = BufferedReader(InputStreamReader(context.resources.openRawResource(rawName)))
+                var line: String?
+                val result = StringBuilder()
+                while (reader.readLine().also { line = it } != null) {
+                    result.append(line).append("\n")
+                }
+                reader.close()
+                emitter.onNext(result.toString())
+                emitter.onComplete()
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/HistoryUtils.kt b/app/src/main/java/com/example/firstapp/utils/HistoryUtils.kt
new file mode 100644
index 0000000..5c56dc2
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/HistoryUtils.kt
@@ -0,0 +1,115 @@
+package com.example.firstapp.utils
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import java.io.*
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/**
+ * 转发历史工具类
+ *
+ * @author pppscn
+ * @since 2022年5月9日
+ */
+@Suppress("UNCHECKED_CAST", "unused")
+class HistoryUtils<T>(private val name: String, private val default: T) : ReadWriteProperty<Any?, T> {
+
+    companion object {
+        lateinit var preference: SharedPreferences
+
+        fun init(context: Context) {
+            preference = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                val directBootContext: Context = context.createDeviceProtectedStorageContext()
+                directBootContext.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
+            } else {
+                context.getSharedPreferences(context.packageName + ".history", Context.MODE_PRIVATE)
+            }
+        }
+
+        //删除全部数据
+        fun clearPreference() = preference.edit().clear().apply()
+
+        //根据key删除存储数据
+        fun clearPreference(key: String) = preference.edit().remove(key).commit()
+    }
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+        return putPreference(name, value)
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        return getPreference(name, default)
+    }
+
+    /**
+     * 查找数据 返回给调用方法一个具体的对象
+     * 如果查找不到类型就采用反序列化方法来返回类型
+     * default是默认对象 以防止会返回空对象的异常
+     * 即如果name没有查找到value 就返回默认的序列化对象,然后经过反序列化返回
+     */
+    private fun getPreference(name: String, default: T): T = with(preference) {
+        val res: Any = when (default) {
+            is Long -> getLong(name, default)
+            is String -> this.getString(name, default)!!
+            is Int -> getInt(name, default)
+            is Boolean -> getBoolean(name, default)
+            is Float -> getFloat(name, default)
+            //else -> throw IllegalArgumentException("This type can be get from Preferences")
+            else -> deSerialization(getString(name, serialize(default)).toString())
+        }
+        return res as T
+    }
+
+    private fun putPreference(name: String, value: T) = with(preference.edit()) {
+        when (value) {
+            is Long -> putLong(name, value)
+            is Int -> putInt(name, value)
+            is String -> putString(name, value)
+            is Boolean -> putBoolean(name, value)
+            is Float -> putFloat(name, value)
+            //else -> throw IllegalArgumentException("This type can be saved into Preferences")
+            else -> putString(name, serialize(value))
+        }.apply()
+    }
+
+    /**
+     * 序列化对象
+     * @throws IOException
+     */
+    @Throws(IOException::class)
+    private fun <T> serialize(obj: T): String {
+        val byteArrayOutputStream = ByteArrayOutputStream()
+        val objectOutputStream = ObjectOutputStream(
+            byteArrayOutputStream
+        )
+        objectOutputStream.writeObject(obj)
+        var serStr = byteArrayOutputStream.toString("ISO-8859-1")
+        serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
+        objectOutputStream.close()
+        byteArrayOutputStream.close()
+        return serStr
+    }
+
+    /**
+     * 反序列化对象
+     * @param str
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    @Throws(IOException::class, ClassNotFoundException::class)
+    private fun <T> deSerialization(str: String): T {
+        val redStr = java.net.URLDecoder.decode(str, "UTF-8")
+        val byteArrayInputStream = ByteArrayInputStream(
+            redStr.toByteArray(charset("ISO-8859-1"))
+        )
+        val objectInputStream = ObjectInputStream(
+            byteArrayInputStream
+        )
+        val obj = objectInputStream.readObject() as T
+        objectInputStream.close()
+        byteArrayInputStream.close()
+        return obj
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/KeepAliveUtils.kt b/app/src/main/java/com/example/firstapp/utils/KeepAliveUtils.kt
new file mode 100644
index 0000000..c1bf255
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/KeepAliveUtils.kt
@@ -0,0 +1,52 @@
+package com.example.firstapp.utils
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ResolveInfo
+import android.net.Uri
+import android.os.Build
+import android.os.PowerManager
+import android.provider.Settings
+import androidx.annotation.RequiresApi
+import com.example.firstapp.R
+
+@Suppress("DEPRECATION")
+class KeepAliveUtils private constructor() {
+
+    companion object {
+        fun isIgnoreBatteryOptimization(activity: Activity): Boolean {
+            //安卓6.0以下没有忽略电池优化
+            return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+                true
+            } else try {
+                val powerManager: PowerManager = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
+                powerManager.isIgnoringBatteryOptimizations(activity.packageName)
+            } catch (e: Exception) {
+//                XToastUtils.error(R.string.unsupport)
+                false
+            }
+        }
+
+        @RequiresApi(api = Build.VERSION_CODES.M)
+        fun ignoreBatteryOptimization(activity: Activity) {
+            try {
+                if (isIgnoreBatteryOptimization(activity)) {
+                    return
+                }
+                @SuppressLint("BatteryLife") val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
+                intent.data = Uri.parse("package:" + activity.packageName)
+                val resolveInfo: ResolveInfo? = activity.packageManager.resolveActivity(intent, 0)
+                if (resolveInfo != null) {
+                    activity.startActivity(intent)
+                } else {
+//                    XToastUtils.error(R.string.unsupport)
+                }
+            } catch (e: Exception) {
+//                XToastUtils.error(R.string.unsupport)
+            }
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/LocationUtils.kt b/app/src/main/java/com/example/firstapp/utils/LocationUtils.kt
new file mode 100644
index 0000000..2e17340
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/LocationUtils.kt
@@ -0,0 +1,72 @@
+package com.example.firstapp.utils
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.location.LocationManager
+import android.os.Build
+import android.provider.Settings
+import androidx.core.content.ContextCompat
+
+@Suppress("DEPRECATION")
+object LocationUtils {
+
+    private const val LOCATION_MODE_OFF = 0
+
+    private fun hasLocationPermission(context: Context): Boolean {
+        val hasPermission = ContextCompat.checkSelfPermission(
+            context,
+            android.Manifest.permission.ACCESS_FINE_LOCATION
+        ) == PackageManager.PERMISSION_GRANTED
+        com.example.firstapp.utils.Log.d("LocationUtils", "hasLocationPermission: $hasPermission")
+        return hasPermission
+    }
+
+    fun isLocationEnabled(context: Context): Boolean {
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
+            com.example.firstapp.utils.Log.d(
+                "LocationUtils",
+                "isLocationEnabled: ${locationManager?.isLocationEnabled}"
+            )
+            locationManager?.isLocationEnabled == true
+        } else {
+            try {
+                val locationMode = Settings.Secure.getInt(
+                    context.contentResolver,
+                    Settings.Secure.LOCATION_MODE
+                )
+                com.example.firstapp.utils.Log.d(
+                    "LocationUtils",
+                    "isLocationEnabled: locationMode=$locationMode"
+                )
+                locationMode != com.example.firstapp.utils.LocationUtils.LOCATION_MODE_OFF
+            } catch (e: Settings.SettingNotFoundException) {
+                false
+            }
+        }
+    }
+
+    fun hasLocationCapability(context: Context): Boolean {
+        val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
+
+        // 检查是否有位置权限
+        if (!com.example.firstapp.utils.LocationUtils.hasLocationPermission(context)) {
+            com.example.firstapp.utils.Log.e(
+                "LocationUtils",
+                "hasLocationCapability: no location permission"
+            )
+            return false
+        }
+
+        // 检查是否有定位能力
+        val hasGpsProvider = locationManager?.isProviderEnabled(LocationManager.GPS_PROVIDER) == true
+        val hasNetworkProvider = locationManager?.isProviderEnabled(LocationManager.NETWORK_PROVIDER) == true
+        val hasPassiveProvider = locationManager?.isProviderEnabled(LocationManager.PASSIVE_PROVIDER) == true
+
+        com.example.firstapp.utils.Log.d(
+            "LocationUtils",
+            "hasLocationCapability: hasGpsProvider=$hasGpsProvider, hasNetworkProvider=$hasNetworkProvider, hasPassiveProvider=$hasPassiveProvider"
+        )
+        return hasGpsProvider || hasNetworkProvider || hasPassiveProvider
+    }
+}
diff --git a/app/src/main/java/com/example/firstapp/utils/Log.kt b/app/src/main/java/com/example/firstapp/utils/Log.kt
new file mode 100644
index 0000000..52467d6
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/Log.kt
@@ -0,0 +1,161 @@
+package com.example.firstapp.utils
+
+import android.content.Context
+import android.os.Build
+import com.example.firstapp.App
+import java.io.File
+import java.io.FileWriter
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+import android.util.Log as AndroidLog
+
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+object Log {
+    const val ASSERT = 7
+    const val DEBUG = 3
+    const val ERROR = 6
+    const val INFO = 4
+    const val VERBOSE = 2
+    const val WARN = 5
+
+    private const val TAG = "Logger"
+    private var logFile: File? = null
+    private lateinit var appContext: Context
+    private var initDate: String = ""
+
+    fun init(context: Context) {
+        appContext = context
+        createLogFile()
+    }
+
+    private fun createLogFile() {
+        val currentDate = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date())
+        if (currentDate != initDate || logFile == null || !logFile!!.exists()) {
+            initDate = currentDate
+            val logPath = appContext.cacheDir.absolutePath + "/logs"
+            val logDir = File(logPath)
+            if (!logDir.exists()) logDir.mkdirs()
+            logFile = File(logPath, "log_$currentDate.txt")
+        }
+    }
+
+    fun logToFile(level: String, tag: String, message: String) {
+        if (Build.DEVICE == null) return
+        
+        if (!::appContext.isInitialized) {
+            throw IllegalStateException("Log not initialized. Call init(context) first.")
+        }
+
+        if (!App.isDebug) return
+
+        Thread {
+            try {
+                createLogFile()
+                logFile?.let { file ->
+                    try {
+                        val logTimeStamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())
+                        val logWriter = FileWriter(file, true)
+                        logWriter.append("$logTimeStamp | $level | $tag | $message\n\n")
+                        logWriter.close()
+                    } catch (e: Exception) {
+                        AndroidLog.e(TAG, "Error writing to file: ${e.message}")
+                    }
+                }
+            } catch (e: Exception) {
+                AndroidLog.e(TAG, "Error writing to file: ${e.message}")
+            }
+        }.start()
+    }
+
+    fun v(tag: String, message: String) {
+        AndroidLog.v(tag, message)
+        logToFile("V", tag, message)
+    }
+
+    fun v(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.v(tag, logMessage)
+        logToFile("V", tag, logMessage)
+    }
+
+    fun d(tag: String, message: String) {
+        AndroidLog.d(tag, message)
+        logToFile("D", tag, message)
+    }
+
+    fun d(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.d(tag, logMessage)
+        logToFile("D", tag, logMessage)
+    }
+
+    fun i(tag: String, message: String) {
+        AndroidLog.d(tag, message)
+        logToFile("I", tag, message)
+    }
+
+    fun i(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.d(tag, logMessage)
+        logToFile("I", tag, logMessage)
+    }
+
+    fun w(tag: String, message: String) {
+        AndroidLog.w(tag, message)
+        logToFile("W", tag, message)
+    }
+
+    fun w(tag: String, throwable: Throwable) {
+        val logMessage = getStackTraceString(throwable)
+        AndroidLog.w(tag, logMessage)
+        logToFile("W", tag, logMessage)
+    }
+
+    fun w(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.w(tag, logMessage)
+        logToFile("W", tag, logMessage)
+    }
+
+    fun e(tag: String, message: String) {
+        AndroidLog.e(tag, message)
+        logToFile("E", tag, message)
+    }
+
+    fun e(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.e(tag, logMessage)
+        logToFile("E", tag, logMessage)
+    }
+
+    fun wtf(tag: String, message: String) {
+        AndroidLog.wtf(tag, message)
+        logToFile("WTF", tag, message)
+    }
+
+    fun wtf(tag: String, throwable: Throwable) {
+        val logMessage = getStackTraceString(throwable)
+        AndroidLog.wtf(tag, logMessage)
+        logToFile("WTF", tag, logMessage)
+    }
+
+    fun wtf(tag: String, message: String, throwable: Throwable) {
+        val logMessage = "${message}\n${getStackTraceString(throwable)}"
+        AndroidLog.wtf(tag, logMessage)
+        logToFile("WTF", tag, logMessage)
+    }
+
+    fun getStackTraceString(throwable: Throwable): String {
+        return AndroidLog.getStackTraceString(throwable)
+    }
+
+    fun isLoggable(tag: String?, level: Int): Boolean {
+        return AndroidLog.isLoggable(tag, level)
+    }
+
+    fun println(priority: Int, tag: String, message: String) {
+        AndroidLog.println(priority, tag, message)
+        logToFile("P", tag, message)
+    }
+}
diff --git a/app/src/main/java/com/example/firstapp/utils/RSACrypt.kt b/app/src/main/java/com/example/firstapp/utils/RSACrypt.kt
new file mode 100644
index 0000000..1668ce7
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/RSACrypt.kt
@@ -0,0 +1,209 @@
+package com.example.firstapp.utils
+
+import java.io.ByteArrayOutputStream
+import java.security.KeyFactory
+import java.security.PrivateKey
+import java.security.PublicKey
+import java.security.spec.PKCS8EncodedKeySpec
+import java.security.spec.X509EncodedKeySpec
+import javax.crypto.Cipher
+
+/**
+ * 非对称加密RSA加密和解密
+ */
+object RSACrypt {
+
+    private const val TRANSFORMATION = "RSA"
+    private const val ENCRYPT_MAX_SIZE = 245
+    private const val DECRYPT_MAX_SIZE = 256
+
+    /**
+     * 私钥加密
+     * @param input 原文
+     * @param privateKey 私钥
+     */
+    fun encryptByPrivateKey(input: String, privateKey: PrivateKey): String {
+
+        //创建cipher对象
+        val cipher = Cipher.getInstance(TRANSFORMATION)
+        //初始化cipher
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey)
+
+        //****非对称加密****
+        val byteArray = input.toByteArray()
+
+        //分段加密
+        var temp: ByteArray?
+        var offset = 0 //当前偏移的位置
+
+        val outputStream = ByteArrayOutputStream()
+
+        //拆分input
+        while (byteArray.size - offset > 0) {
+            //每次最大加密245个字节
+            if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) {
+                //剩余部分大于245
+                //加密完整245
+                temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE)
+                //重新计算偏移位置
+                offset += ENCRYPT_MAX_SIZE
+            } else {
+                //加密最后一块
+                temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
+                //重新计算偏移位置
+                offset = byteArray.size
+            }
+            //存储到临时的缓冲区
+            outputStream.write(temp)
+        }
+        outputStream.close()
+
+        return Base64.encode(outputStream.toByteArray())
+
+    }
+
+    /**
+     * 公钥加密
+     * @param input 原文
+     * @param publicKey 公钥
+     */
+    fun encryptByPublicKey(input: String, publicKey: PublicKey): String {
+
+        //创建cipher对象
+        val cipher = Cipher.getInstance(TRANSFORMATION)
+        //初始化cipher
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey)
+
+        //****非对称加密****
+        val byteArray = input.toByteArray()
+
+        var temp: ByteArray?
+        var offset = 0 //当前偏移的位置
+
+        val outputStream = ByteArrayOutputStream()
+
+        //拆分input
+        while (byteArray.size - offset > 0) {
+            //每次最大加密117个字节
+            if (byteArray.size - offset >= ENCRYPT_MAX_SIZE) {
+                //剩余部分大于117
+                //加密完整117
+                temp = cipher.doFinal(byteArray, offset, ENCRYPT_MAX_SIZE)
+                //重新计算偏移位置
+                offset += ENCRYPT_MAX_SIZE
+            } else {
+                //加密最后一块
+                temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
+                //重新计算偏移位置
+                offset = byteArray.size
+            }
+            //存储到临时的缓冲区
+            outputStream.write(temp)
+        }
+        outputStream.close()
+
+        return Base64.encode(outputStream.toByteArray())
+
+    }
+
+    /**
+     * 私钥解密
+     * @param input 秘文
+     * @param privateKey 私钥
+     */
+    fun decryptByPrivateKey(input: String, privateKey: PrivateKey): String {
+
+        //创建cipher对象
+        val cipher = Cipher.getInstance(TRANSFORMATION)
+        //初始化cipher
+        cipher.init(Cipher.DECRYPT_MODE, privateKey)
+
+        //****非对称加密****
+        val byteArray = Base64.decode(input)
+
+        //分段解密
+        var temp: ByteArray?
+        var offset = 0 //当前偏移的位置
+
+        val outputStream = ByteArrayOutputStream()
+
+        //拆分input
+        while (byteArray.size - offset > 0) {
+            //每次最大解密256个字节
+            if (byteArray.size - offset >= DECRYPT_MAX_SIZE) {
+
+                temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE)
+                //重新计算偏移位置
+                offset += DECRYPT_MAX_SIZE
+            } else {
+                //加密最后一块
+                temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
+                //重新计算偏移位置
+                offset = byteArray.size
+            }
+            //存储到临时的缓冲区
+            outputStream.write(temp)
+        }
+        outputStream.close()
+
+        return String(outputStream.toByteArray())
+
+    }
+
+    /**
+     * 公钥解密
+     * @param input 秘文
+     * @param publicKey 公钥
+     */
+    fun decryptByPublicKey(input: String, publicKey: PublicKey): String {
+
+        //创建cipher对象
+        val cipher = Cipher.getInstance(TRANSFORMATION)
+        //初始化cipher
+        cipher.init(Cipher.DECRYPT_MODE, publicKey)
+
+        //****非对称加密****
+        val byteArray = Base64.decode(input)
+
+        //分段解密
+        var temp: ByteArray?
+        var offset = 0 //当前偏移的位置
+
+        val outputStream = ByteArrayOutputStream()
+
+        //拆分input
+        while (byteArray.size - offset > 0) {
+            //每次最大解密256个字节
+            if (byteArray.size - offset >= DECRYPT_MAX_SIZE) {
+
+                temp = cipher.doFinal(byteArray, offset, DECRYPT_MAX_SIZE)
+                //重新计算偏移位置
+                offset += DECRYPT_MAX_SIZE
+            } else {
+                //加密最后一块
+                temp = cipher.doFinal(byteArray, offset, byteArray.size - offset)
+                //重新计算偏移位置
+                offset = byteArray.size
+            }
+            //存储到临时的缓冲区
+            outputStream.write(temp)
+        }
+        outputStream.close()
+
+        return String(outputStream.toByteArray())
+
+    }
+
+    fun getPrivateKey(privateKeyStr: String): PrivateKey {
+        //字符串转成秘钥对对象
+        val generator = KeyFactory.getInstance("RSA")
+        return generator.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privateKeyStr)))
+    }
+
+    fun getPublicKey(publicKeyStr: String): PublicKey {
+        //字符串转成秘钥对对象
+        val kf = KeyFactory.getInstance("RSA")
+        return kf.generatePublic(X509EncodedKeySpec(Base64.decode(publicKeyStr)))
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/RandomUtils.kt b/app/src/main/java/com/example/firstapp/utils/RandomUtils.kt
new file mode 100644
index 0000000..ac098df
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/RandomUtils.kt
@@ -0,0 +1,293 @@
+package com.example.firstapp.utils
+
+import android.graphics.Color
+import android.text.TextUtils
+import java.util.*
+
+/**
+ * <pre>
+ * desc   : Random Utils
+ * author : xuexiang
+ * time   : 2018/4/28 上午12:41
+</pre> *
+ *
+ * Shuffling algorithm
+ *  * [.shuffle] Shuffling algorithm, Randomly permutes the specified array using a default source of
+ * randomness
+ *  * [.shuffle] Shuffling algorithm, Randomly permutes the specified array
+ *  * [.shuffle] Shuffling algorithm, Randomly permutes the specified int array using a default source of
+ * randomness
+ *  * [.shuffle] Shuffling algorithm, Randomly permutes the specified int array
+ *
+ *
+ * get random int
+ *  * [.getRandom] get random int between 0 and max
+ *  * [.getRandom] get random int between min and max
+ *
+ *
+ * get random numbers or letters
+ *  * [.getRandomCapitalLetters] get a fixed-length random string, its a mixture of uppercase letters
+ *  * [.getRandomLetters] get a fixed-length random string, its a mixture of uppercase and lowercase letters
+ *
+ *  * [.getRandomLowerCaseLetters] get a fixed-length random string, its a mixture of lowercase letters
+ *  * [.getRandomNumbers] get a fixed-length random string, its a mixture of numbers
+ *  * [.getRandomNumbersAndLetters] get a fixed-length random string, its a mixture of uppercase, lowercase
+ * letters and numbers
+ *  * [.getRandom] get a fixed-length random string, its a mixture of chars in source
+ *  * [.getRandom] get a fixed-length random string, its a mixture of chars in sourceChar
+ *
+ *
+ */
+@Suppress("MemberVisibilityCanBePrivate", "unused")
+class RandomUtils private constructor() {
+    companion object {
+        private const val NUMBERS_AND_LETTERS =
+            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        private const val NUMBERS = "0123456789"
+        private const val LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        private const val CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        private const val LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
+
+        /**
+         * 在数字和英文字母中获取一个定长的随机字符串
+         *
+         * @param length 长度
+         * @return 随机字符串
+         * @see RandomUtils.getRandom
+         */
+        @JvmStatic
+        fun getRandomNumbersAndLetters(length: Int): String? {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                com.example.firstapp.utils.RandomUtils.Companion.NUMBERS_AND_LETTERS,
+                length
+            )
+        }
+
+        /**
+         * 在数字中获取一个定长的随机字符串
+         *
+         * @param length 长度
+         * @return 随机数字符串
+         * @see RandomUtils.getRandom
+         */
+        fun getRandomNumbers(length: Int): String? {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                com.example.firstapp.utils.RandomUtils.Companion.NUMBERS,
+                length
+            )
+        }
+
+        /**
+         * 在英文字母中获取一个定长的随机字符串
+         *
+         * @param length 长度
+         * @return 随机字母字符串
+         * @see RandomUtils.getRandom
+         */
+        fun getRandomLetters(length: Int): String? {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                com.example.firstapp.utils.RandomUtils.Companion.LETTERS,
+                length
+            )
+        }
+
+        /**
+         * 在大写英文字母中获取一个定长的随机字符串
+         *
+         * @param length 长度
+         * @return 随机字符串 只包含大写字母
+         * @see RandomUtils.getRandom
+         */
+        fun getRandomCapitalLetters(length: Int): String? {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                com.example.firstapp.utils.RandomUtils.Companion.CAPITAL_LETTERS,
+                length
+            )
+        }
+
+        /**
+         * 在小写英文字母中获取一个定长的随机字符串
+         *
+         * @param length 长度
+         * @return 随机字符串 只包含小写字母
+         * @see RandomUtils.getRandom
+         */
+        fun getRandomLowerCaseLetters(length: Int): String? {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                com.example.firstapp.utils.RandomUtils.Companion.LOWER_CASE_LETTERS,
+                length
+            )
+        }
+
+        /**
+         * 在一个字符数组源中获取一个定长的随机字符串
+         *
+         * @param source 源字符串
+         * @param length 长度
+         * @return
+         *  * if source is null or empty, return null
+         *  * else see [RandomUtils.getRandom]
+         *
+         */
+        fun getRandom(source: String, length: Int): String? {
+            return if (TextUtils.isEmpty(source)) null else com.example.firstapp.utils.RandomUtils.Companion.getRandom(
+                source.toCharArray(),
+                length
+            )
+        }
+
+        /**
+         * 在一个字符数组源中获取一个定长的随机字符串
+         *
+         * @param sourceChar 字符数组源
+         * @param length     长度
+         * @return
+         *  * if sourceChar is null or empty, return null
+         *  * if length less than 0, return null
+         *
+         */
+        fun getRandom(sourceChar: CharArray?, length: Int): String? {
+            if (sourceChar == null || sourceChar.isEmpty() || length < 0) {
+                return null
+            }
+            val str = StringBuilder(length)
+            val random = Random()
+            for (i in 0 until length) {
+                str.append(sourceChar[random.nextInt(sourceChar.size)])
+            }
+            return str.toString()
+        }
+
+        /**
+         * get random int between 0 and max
+         *
+         * @param max 最大随机数
+         * @return
+         *  * if max <= 0, return 0
+         *  * else return random int between 0 and max
+         *
+         */
+        fun getRandom(max: Int): Int {
+            return com.example.firstapp.utils.RandomUtils.Companion.getRandom(0, max)
+        }
+
+        /**
+         * get random int between min and max
+         *
+         * @param min 最小随机数
+         * @param max 最大随机数
+         * @return
+         *  * if min > max, return 0
+         *  * if min == max, return min
+         *  * else return random int between min and max
+         *
+         */
+        fun getRandom(min: Int, max: Int): Int {
+            if (min > max) {
+                return 0
+            }
+            return if (min == max) {
+                min
+            } else min + Random().nextInt(max - min)
+        }
+
+        /**
+         * 获取随机颜色
+         *
+         * @return
+         */
+        val randomColor: Int
+            get() {
+                val random = Random()
+                val r = random.nextInt(256)
+                val g = random.nextInt(256)
+                val b = random.nextInt(256)
+                return Color.rgb(r, g, b)
+            }
+
+        /**
+         * 随机打乱数组中的内容
+         *
+         * @param objArray
+         * @return
+         */
+        fun shuffle(objArray: Array<Any?>?): Boolean {
+            return if (objArray == null) {
+                false
+            } else com.example.firstapp.utils.RandomUtils.Companion.shuffle(
+                objArray,
+                com.example.firstapp.utils.RandomUtils.Companion.getRandom(objArray.size)
+            )
+        }
+
+        /**
+         * 随机打乱数组中的内容
+         *
+         * @param objArray
+         * @param shuffleCount
+         * @return
+         */
+        private fun shuffle(objArray: Array<Any?>?, shuffleCount: Int): Boolean {
+            var length = 0
+            if (objArray == null || shuffleCount < 0 || objArray.size.also {
+                    length = it
+                } < shuffleCount) {
+                return false
+            }
+            for (i in 1..shuffleCount) {
+                val random = com.example.firstapp.utils.RandomUtils.Companion.getRandom(length - i)
+                val temp = objArray[length - i]
+                objArray[length - i] = objArray[random]
+                objArray[random] = temp
+            }
+            return true
+        }
+
+        /**
+         * 随机打乱数组中的内容
+         *
+         * @param intArray
+         * @return
+         */
+        fun shuffle(intArray: IntArray?): IntArray? {
+            return if (intArray == null) {
+                null
+            } else com.example.firstapp.utils.RandomUtils.Companion.shuffle(
+                intArray,
+                com.example.firstapp.utils.RandomUtils.Companion.getRandom(intArray.size)
+            )
+        }
+
+        /**
+         * 随机打乱数组中的内容
+         *
+         * @param intArray
+         * @param shuffleCount
+         * @return
+         */
+        fun shuffle(intArray: IntArray?, shuffleCount: Int): IntArray? {
+            var length = 0
+            if (intArray == null || shuffleCount < 0 || intArray.size.also {
+                    length = it
+                } < shuffleCount) {
+                return null
+            }
+            val out = IntArray(shuffleCount)
+            for (i in 1..shuffleCount) {
+                val random = com.example.firstapp.utils.RandomUtils.Companion.getRandom(length - i)
+                out[i - 1] = intArray[random]
+                val temp = intArray[length - i]
+                intArray[length - i] = intArray[random]
+                intArray[random] = temp
+            }
+            return out
+        }
+    }
+
+    /**
+     * Don't let anyone instantiate this class.
+     */
+    init {
+        throw Error("Do not need instantiate!")
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/SM4Crypt.kt b/app/src/main/java/com/example/firstapp/utils/SM4Crypt.kt
new file mode 100644
index 0000000..1cde9f7
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/SM4Crypt.kt
@@ -0,0 +1,62 @@
+package com.example.firstapp.utils
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import java.security.SecureRandom
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+/**
+ * SM4分组密码算法是我国自主设计的分组对称密码算法
+ */
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+object SM4Crypt {
+
+    const val SM4_CBC_NOPADDING = "SM4/CBC/NoPadding"
+    const val SM4_CBC_PKCS5 = "SM4/CBC/PKCS5Padding"
+    const val SM4_CBC_PKCS7 = "SM4/CBC/PKCS7Padding"
+    const val SM4_ECB_NOPADDING = "SM4/ECB/NoPadding"
+    const val SM4_ECB_PKCS5 = "SM4/ECB/PKCS5Padding"
+    const val SM4_ECB_PKCS7 = "SM4/ECB/PKCS7Padding"
+    private val BC_PROVIDER = BouncyCastleProvider()
+    private val SM4_CBC_IV = byteArrayOf(3, 5, 6, 9, 6, 9, 5, 9, 3, 5, 6, 9, 6, 9, 5, 9)
+
+    /**
+     * 获取随机密钥
+     */
+    fun createSM4Key(): ByteArray {
+        val seed = ByteArray(16)
+        val random = SecureRandom()
+        random.nextBytes(seed)
+        return seed
+    }
+
+    @JvmOverloads
+    fun encrypt(source: ByteArray, key: ByteArray, mode: String = SM4_CBC_PKCS7, iv: ByteArray? = SM4_CBC_IV): ByteArray {
+        return doSM4(true, source, key, mode, iv)
+    }
+
+    @JvmOverloads
+    fun decrypt(source: ByteArray, key: ByteArray, mode: String = SM4_CBC_PKCS7, iv: ByteArray? = SM4_CBC_IV): ByteArray {
+        return doSM4(false, source, key, mode, iv)
+    }
+
+    private fun doSM4(forEncryption: Boolean, source: ByteArray, key: ByteArray, mode: String, iv: ByteArray?): ByteArray {
+        return try {
+            val cryptMode = if (forEncryption) 1 else 2
+            val sm4Key = SecretKeySpec(key, "SM4")
+            val cipher = Cipher.getInstance(mode, BC_PROVIDER)
+            if (iv == null) {
+                cipher.init(cryptMode, sm4Key)
+            } else {
+                val ivParameterSpec = IvParameterSpec(iv)
+                cipher.init(cryptMode, sm4Key, ivParameterSpec)
+            }
+            cipher.doFinal(source)
+        } catch (var9: Exception) {
+            var9.printStackTrace()
+            ByteArray(0)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/SettingUtils.kt b/app/src/main/java/com/example/firstapp/utils/SettingUtils.kt
new file mode 100644
index 0000000..02dd9a9
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/SettingUtils.kt
@@ -0,0 +1,166 @@
+package com.example.firstapp.utils
+
+import android.location.Criteria
+import com.example.firstapp.R
+
+import com.xuexiang.xutil.resource.ResUtils.getString
+
+class SettingUtils private constructor() {
+    companion object {
+
+        //是否启动时检查更新
+        var autoCheckUpdate: Boolean by SharedPreference(AUTO_CHECK_UPDATE, true)
+
+        //是否加入SmsF预览体验计划
+        var joinPreviewProgram: Boolean by SharedPreference(JOIN_PREVIEW_PROGRAM, false)
+
+        //是否同意隐私政策
+        var isAgreePrivacy: Boolean by SharedPreference(IS_AGREE_PRIVACY_KEY, false)
+
+        //是否转发短信
+        var enableSms: Boolean by SharedPreference(SP_ENABLE_SMS, false)
+
+        //是否转发通话
+        var enablePhone: Boolean by SharedPreference(SP_ENABLE_PHONE, false)
+
+        //是否转发通话——来电挂机
+        var enableCallType1: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_1, false)
+
+        //是否转发通话——去电挂机
+        var enableCallType2: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_2, false)
+
+        //是否转发通话——未接来电
+        var enableCallType3: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_3, false)
+
+        //是否转发通话——来电提醒
+        var enableCallType4: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_4, false)
+
+        //是否转发通话——来电接通
+        var enableCallType5: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_5, false)
+
+        //是否转发通话——去电拨出
+        var enableCallType6: Boolean by SharedPreference(SP_ENABLE_CALL_TYPE_6, false)
+
+        //是否转发应用通知
+        var enableAppNotify: Boolean by SharedPreference(SP_ENABLE_APP_NOTIFY, false)
+
+        //是否接受短信指令
+        var enableSmsCommand: Boolean by SharedPreference(SP_ENABLE_SMS_COMMAND, false)
+        var smsCommandSafePhone: String by SharedPreference(SP_SMS_COMMAND_SAFE_PHONE, "")
+
+        //是否转发应用通知——自动消除通知
+        var enableCancelAppNotify: Boolean by SharedPreference(SP_ENABLE_CANCEL_APP_NOTIFY, false)
+
+        //是否转发应用通知——自动消除额外APP通知
+        var cancelExtraAppNotify: String by SharedPreference(SP_CANCEL_EXTRA_APP_NOTIFY, "")
+
+        //是否转发应用通知——仅锁屏状态
+        var enableNotUserPresent: Boolean by SharedPreference(SP_ENABLE_NOT_USER_PRESENT, false)
+
+        //是否加载应用列表
+        var enableLoadAppList: Boolean by SharedPreference(ENABLE_LOAD_APP_LIST, false)
+
+        //是否加载应用列表——用户应用
+        var enableLoadUserAppList: Boolean by SharedPreference(ENABLE_LOAD_USER_APP_LIST, false)
+
+        //是否加载应用列表——系统应用
+        var enableLoadSystemAppList: Boolean by SharedPreference(ENABLE_LOAD_SYSTEM_APP_LIST, false)
+
+        //过滤多久内重复消息
+        var duplicateMessagesLimits: Int by SharedPreference(SP_DUPLICATE_MESSAGES_LIMITS, 0)
+
+        //免打扰(禁用转发)时间段——开始
+        var silentPeriodStart: Int by SharedPreference(SP_SILENT_PERIOD_START, 0)
+
+        //免打扰(禁用转发)时间段——结束
+        var silentPeriodEnd: Int by SharedPreference(SP_SILENT_PERIOD_END, 0)
+
+        //免打扰(禁用转发)时间段——记录日志
+        var enableSilentPeriodLogs: Boolean by SharedPreference(SP_ENABLE_SILENT_PERIOD_LOGS, false)
+
+        //是否不在最近任务列表中显示
+        var enableExcludeFromRecents: Boolean by SharedPreference(SP_ENABLE_EXCLUDE_FROM_RECENTS, false)
+
+        //是否转发应用通知
+        var enableCactus: Boolean by SharedPreference(SP_ENABLE_CACTUS, false)
+
+        //是否播放静音音乐
+        var enablePlaySilenceMusic: Boolean by SharedPreference(SP_ENABLE_PLAY_SILENCE_MUSIC, false)
+
+        //是否启用1像素
+        var enableOnePixelActivity: Boolean by SharedPreference(SP_ENABLE_ONE_PIXEL_ACTIVITY, false)
+
+        //请求接口失败重试次数
+        var requestRetryTimes: Int by SharedPreference(SP_REQUEST_RETRY_TIMES, 0)
+
+        //请求接口失败重试间隔(秒)
+        var requestDelayTime: Int by SharedPreference(SP_REQUEST_DELAY_TIME, 1)
+
+        //请求接口失败超时时间(秒)
+        var requestTimeout: Int by SharedPreference(SP_REQUEST_TIMEOUT, 10)
+
+        //通知内容
+        var notifyContent: String by SharedPreference(SP_NOTIFY_CONTENT, getString(R.string.notification_content))
+
+        //设备名称
+        var extraDeviceMark: String by SharedPreference(SP_EXTRA_DEVICE_MARK, "")
+
+        //SIM1主键
+        var subidSim1: Int by SharedPreference(SP_SUBID_SIM1, 0)
+
+        //SIM2主键
+        var subidSim2: Int by SharedPreference(SP_SUBID_SIM2, 0)
+
+        //SIM1备注
+        var extraSim1: String by SharedPreference(SP_EXTRA_SIM1, "")
+
+        //SIM2备注
+        var extraSim2: String by SharedPreference(SP_EXTRA_SIM2, "")
+
+        //是否启用自定义模板
+        var enableSmsTemplate: Boolean by SharedPreference(SP_ENABLE_SMS_TEMPLATE, false)
+
+        //自定义模板
+        var smsTemplate: String by SharedPreference(SP_SMS_TEMPLATE, "")
+
+        //是否纯客户端模式
+        var enablePureClientMode: Boolean by SharedPreference(SP_PURE_CLIENT_MODE, false)
+
+        //是否纯任务模式
+        var enablePureTaskMode: Boolean by SharedPreference(SP_PURE_TASK_MODE, false)
+
+        //是否调试模式
+        var enableDebugMode: Boolean by SharedPreference(SP_DEBUG_MODE, false)
+
+        //是否启用定位功能
+        var enableLocation: Boolean by SharedPreference(SP_LOCATION, false)
+
+        //设置位置精度:高精度
+        var locationAccuracy: Int by SharedPreference(SP_LOCATION_ACCURACY, Criteria.ACCURACY_FINE)
+
+        //设置电量消耗:低电耗
+        var locationPowerRequirement: Int by SharedPreference(SP_LOCATION_POWER_REQUIREMENT, Criteria.POWER_LOW)
+
+        //设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒
+        var locationMinInterval: Long by SharedPreference(SP_LOCATION_MIN_INTERVAL, 10000L)
+
+        //设置位置更新最小距离(单位:米);默认距离:0米
+        var locationMinDistance: Int by SharedPreference(SP_LOCATION_MIN_DISTANCE, 0)
+
+        //是否跟随系统语言
+        //var isFlowSystemLanguage: Boolean by SharedPreference(SP_IS_FLOW_SYSTEM_LANGUAGE, false)
+
+        //是否启用发现蓝牙设备服务
+        var enableBluetooth: Boolean by SharedPreference(SP_BLUETOOTH, false)
+
+        //扫描蓝牙设备间隔
+        var bluetoothScanInterval: Long by SharedPreference(SP_BLUETOOTH_SCAN_INTERVAL, 10000L)
+
+        //是否忽略匿名设备
+        var bluetoothIgnoreAnonymous: Boolean by SharedPreference(SP_BLUETOOTH_IGNORE_ANONYMOUS, true)
+    }
+
+    init {
+        throw UnsupportedOperationException("u can't instantiate me...")
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/SharedPreference.kt b/app/src/main/java/com/example/firstapp/utils/SharedPreference.kt
new file mode 100644
index 0000000..733a6b5
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/SharedPreference.kt
@@ -0,0 +1,131 @@
+package com.example.firstapp.utils
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import java.io.*
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+@Suppress("unused", "UNCHECKED_CAST")
+class SharedPreference<T>(private val name: String, private val default: T) : ReadWriteProperty<Any?, T> {
+
+    companion object {
+        lateinit var preference: SharedPreferences
+
+        fun init(context: Context) {
+            preference = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                val directBootContext: Context = context.createDeviceProtectedStorageContext()
+                directBootContext.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+            } else {
+                context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+            }
+        }
+
+        //删除全部数据
+        fun clearPreference() = preference.edit().clear().apply()
+
+        //根据key删除存储数据
+        fun clearPreference(key: String) = preference.edit().remove(key).commit()
+
+        //导出全部数据
+        fun exportPreference(): String {
+            return serialize(preference.all)
+        }
+
+        //导入全部数据
+        fun importPreference(data: String) {
+            val map = deSerialization<Map<String, Any>>(data)
+            val editor = preference.edit()
+            for ((key, value) in map) {
+                when (value) {
+                    is Long -> editor.putLong(key, value)
+                    is Int -> editor.putInt(key, value)
+                    is String -> editor.putString(key, value)
+                    is Boolean -> editor.putBoolean(key, value)
+                    is Float -> editor.putFloat(key, value)
+                    else -> editor.putString(key, serialize(value))
+                }
+            }
+            editor.apply()
+        }
+
+        /**
+         * 序列化对象
+         * @throws IOException
+         */
+        @Throws(IOException::class)
+        private fun <T> serialize(obj: T): String {
+            val byteArrayOutputStream = ByteArrayOutputStream()
+            val objectOutputStream = ObjectOutputStream(
+                byteArrayOutputStream
+            )
+            objectOutputStream.writeObject(obj)
+            var serStr = byteArrayOutputStream.toString("ISO-8859-1")
+            serStr = java.net.URLEncoder.encode(serStr, "UTF-8")
+            objectOutputStream.close()
+            byteArrayOutputStream.close()
+            return serStr
+        }
+
+        /**
+         * 反序列化对象
+         * @param str
+         * @throws IOException
+         * @throws ClassNotFoundException
+         */
+        @Throws(IOException::class, ClassNotFoundException::class)
+        private fun <T> deSerialization(str: String): T {
+            val redStr = java.net.URLDecoder.decode(str, "UTF-8")
+            val byteArrayInputStream = ByteArrayInputStream(
+                redStr.toByteArray(charset("ISO-8859-1"))
+            )
+            val objectInputStream = ObjectInputStream(
+                byteArrayInputStream
+            )
+            val obj = objectInputStream.readObject() as T
+            objectInputStream.close()
+            byteArrayInputStream.close()
+            return obj
+        }
+    }
+
+    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+        return putPreference(name, value)
+    }
+
+    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        return getPreference(name, default)
+    }
+
+    /**
+     * 查找数据 返回给调用方法一个具体的对象
+     * 如果查找不到类型就采用反序列化方法来返回类型
+     * default是默认对象 以防止会返回空对象的异常
+     * 即如果name没有查找到value 就返回默认的序列化对象,然后经过反序列化返回
+     */
+    private fun getPreference(name: String, default: T): T = with(preference) {
+        val res: Any = when (default) {
+            is Long -> getLong(name, default)
+            is String -> this.getString(name, default)!!
+            is Int -> getInt(name, default)
+            is Boolean -> getBoolean(name, default)
+            is Float -> getFloat(name, default)
+            //else -> throw IllegalArgumentException("This type can be get from Preferences")
+            else -> deSerialization(getString(name, serialize(default)).toString())
+        }
+        return res as T
+    }
+
+    private fun putPreference(name: String, value: T) = with(preference.edit()) {
+        when (value) {
+            is Long -> putLong(name, value)
+            is Int -> putInt(name, value)
+            is String -> putString(name, value)
+            is Boolean -> putBoolean(name, value)
+            is Float -> putFloat(name, value)
+            //else -> throw IllegalArgumentException("This type can be saved into Preferences")
+            else -> putString(name, serialize(value))
+        }.apply()
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/mail/EmailSender.kt b/app/src/main/java/com/example/firstapp/utils/mail/EmailSender.kt
new file mode 100644
index 0000000..54cb26e
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/mail/EmailSender.kt
@@ -0,0 +1,174 @@
+package com.example.firstapp.utils.mail
+
+import android.text.Html
+import android.text.Spanned
+import com.example.firstapp.utils.Log
+import org.bouncycastle.openpgp.PGPPublicKeyRing
+import org.bouncycastle.openpgp.PGPSecretKeyRing
+import java.io.File
+import java.security.PrivateKey
+import java.security.cert.X509Certificate
+import java.util.Properties
+import javax.mail.Authenticator
+import javax.mail.PasswordAuthentication
+
+@Suppress("PrivatePropertyName", "DEPRECATION")
+class EmailSender(
+    // SMTP参数
+    private val host: String, // SMTP服务器地址
+    private val port: String, // SMTP服务器端口
+    private val from: String, // 发件人邮箱
+    private val password: String, // 发件人邮箱密码/授权码
+    // 邮件参数
+    private val nickname: String, // 发件人昵称
+    private val subject: String, // 邮件主题
+    private val body: CharSequence, // 邮件正文
+    private val attachFiles: MutableList<File> = mutableListOf(), // 附件
+    // 收件人参数
+    private val toAddress: MutableList<String> = mutableListOf(), // 收件人邮箱
+    private val ccAddress: MutableList<String> = mutableListOf(), // 抄送者邮箱
+    private val bccAddress: MutableList<String> = mutableListOf(), // 密送者邮箱
+    // 监听器
+    private val listener: EmailTaskListener? = null,
+    // 安全选项
+    private val openSSL: Boolean = false, //是否开启ssl验证 默认关闭
+    private val sslFactory: String = "javax.net.ssl.SSLSocketFactory", //SSL构建类名
+    private val startTls: Boolean = false, //是否开启starttls加密方式 默认关闭
+    // 邮件加密方式: S/MIME、OpenPGP、Plain(不传证书)
+    private val encryptionProtocol: String = "S/MIME",
+    // 邮件 S/MIME 加密和签名
+    private val recipientX509Cert: X509Certificate? = null, //收件人公钥(用于加密)
+    private val senderPrivateKey: PrivateKey? = null, //发件人私玥(用于签名)
+    private val senderX509Cert: X509Certificate? = null, //发件人公玥(用于签名)
+    //邮件 PGP 加密和签名
+    private var recipientPGPPublicKeyRing: PGPPublicKeyRing? = null, // 收件人公钥(用于加密)
+    private var senderPGPSecretKeyRing: PGPSecretKeyRing? = null, // 发件人私钥(用于签名)
+    private val senderPGPSecretKeyPassword: String = "", // 发件人私钥密码
+) {
+
+    private val TAG: String = EmailSender::class.java.simpleName
+
+    private val properties: Properties = Properties().apply {
+        // 设置邮件服务器的主机名
+        put("mail.smtp.host", host)
+        // 设置邮件服务器的端口号
+        put("mail.smtp.port", port)
+        // 设置是否需要身份验证
+        put("mail.smtp.auth", "true")
+        // 设置是否启用 SSL 连接
+        if (openSSL) {
+            put("mail.smtp.ssl.enable", "true")
+            put("mail.smtp.socketFactory.class", sslFactory)
+        }
+        // 设置是否启用 TLS 连接
+        if (startTls) {
+            put("mail.smtp.starttls.enable", "true")
+        }
+    }
+
+    suspend fun sendEmail() {
+        try {
+            val authenticator = MailAuthenticator(from, password)
+            // 邮件正文
+            val html = try {
+                if (body is Spanned) Html.toHtml(body) else body.toString()
+            } catch (e: Exception) {
+                body.toString()
+            }
+
+            // 发送 S/MIME 邮件
+            when (encryptionProtocol) {
+                "S/MIME" -> {
+                    val smimeUtils = SmimeUtils(
+                        properties,
+                        authenticator,
+                        from,
+                        nickname,
+                        subject,
+                        html,
+                        attachFiles,
+                        toAddress,
+                        ccAddress,
+                        bccAddress,
+                        recipientX509Cert,
+                        senderPrivateKey,
+                        senderX509Cert,
+                    )
+                    val isEncrypt: Boolean = recipientX509Cert != null
+                    val isSign: Boolean = senderX509Cert != null && senderPrivateKey != null
+                    Log.d(TAG, "isEncrypt=$isEncrypt, isSign=$isSign")
+                    val result = when {
+                        isEncrypt && isSign -> smimeUtils.sendSignedAndEncryptedEmail()
+                        isEncrypt -> smimeUtils.sendEncryptedEmail()
+                        isSign -> smimeUtils.sendSignedEmail()
+                        else -> smimeUtils.sendPlainEmail()
+                    }
+                    listener?.onEmailSent(result.first, result.second)
+                }
+
+                "OpenPGP" -> {
+                    // 发送 PGP 邮件
+                    val pgpEmail = PgpUtils(
+                        properties,
+                        authenticator,
+                        from,
+                        nickname,
+                        subject,
+                        html,
+                        attachFiles,
+                        toAddress,
+                        ccAddress,
+                        bccAddress,
+                        recipientPGPPublicKeyRing,
+                        senderPGPSecretKeyRing,
+                        senderPGPSecretKeyPassword,
+                    )
+                    val isEncrypt: Boolean = recipientPGPPublicKeyRing != null
+                    val isSign: Boolean = senderPGPSecretKeyRing != null
+                    Log.d(TAG, "isEncrypt=$isEncrypt, isSign=$isSign")
+                    val result = when {
+                        isEncrypt && isSign -> pgpEmail.sendSignedAndEncryptedEmail()
+                        isEncrypt -> pgpEmail.sendEncryptedEmail()
+                        isSign -> pgpEmail.sendSignedEmail()
+                        else -> pgpEmail.sendPlainEmail()
+                    }
+                    listener?.onEmailSent(result.first, result.second)
+                }
+
+                else -> {
+                    // 发送普通邮件
+                    val simpleEmail = SmimeUtils(
+                        properties,
+                        authenticator,
+                        from,
+                        nickname,
+                        subject,
+                        html,
+                        attachFiles,
+                        toAddress,
+                        ccAddress,
+                        bccAddress,
+                    )
+                    val result = simpleEmail.sendPlainEmail()
+                    listener?.onEmailSent(result.first, result.second)
+                }
+            }
+        } catch (e: Exception) {
+            listener?.onEmailSent(false, "Error sending email: ${e.message}")
+        }
+    }
+
+    interface EmailTaskListener {
+        fun onEmailSent(success: Boolean, message: String)
+    }
+
+    /**
+     * 发件箱auth校验
+     */
+    private class MailAuthenticator(username: String, private var password: String) : Authenticator() {
+        private var userName: String? = username
+        override fun getPasswordAuthentication(): PasswordAuthentication {
+            return PasswordAuthentication(userName, password)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/mail/PgpUtils.kt b/app/src/main/java/com/example/firstapp/utils/mail/PgpUtils.kt
new file mode 100644
index 0000000..7f336e7
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/mail/PgpUtils.kt
@@ -0,0 +1,247 @@
+package com.example.firstapp.utils.mail
+
+import com.example.firstapp.utils.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.bouncycastle.openpgp.PGPPublicKeyRing
+import org.bouncycastle.openpgp.PGPSecretKeyRing
+import org.bouncycastle.util.io.Streams
+import org.pgpainless.PGPainless
+import org.pgpainless.algorithm.DocumentSignatureType
+import org.pgpainless.algorithm.HashAlgorithm
+import org.pgpainless.encryption_signing.EncryptionOptions
+import org.pgpainless.encryption_signing.ProducerOptions
+import org.pgpainless.encryption_signing.SigningOptions
+import org.pgpainless.key.protection.SecretKeyRingProtector
+import org.pgpainless.util.Passphrase
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.InputStream
+import java.security.Security
+import java.util.Date
+import java.util.Properties
+import javax.activation.DataHandler
+import javax.activation.FileDataSource
+import javax.mail.Authenticator
+import javax.mail.Message
+import javax.mail.Session
+import javax.mail.Transport
+import javax.mail.internet.InternetAddress
+import javax.mail.internet.MimeBodyPart
+import javax.mail.internet.MimeMessage
+import javax.mail.internet.MimeMultipart
+import javax.mail.internet.MimeUtility
+import javax.mail.util.ByteArrayDataSource
+
+
+@Suppress("PrivatePropertyName")
+class PgpUtils(
+    private val properties: Properties,
+    private val authenticator: Authenticator,
+    // 邮件参数
+    private val from: String, // 发件人邮箱
+    private val nickname: String, // 发件人昵称
+    private val subject: String, // 邮件主题
+    private val body: String, // 邮件正文
+    private val attachFiles: MutableList<File> = mutableListOf(), // 附件
+    // 收件人参数
+    private val toAddress: MutableList<String> = mutableListOf(), // 收件人邮箱
+    private val ccAddress: MutableList<String> = mutableListOf(), // 抄送者邮箱
+    private val bccAddress: MutableList<String> = mutableListOf(), // 密送者邮箱
+    //邮件 PGP 加密和签名
+    private var recipientPGPPublicKeyRing: PGPPublicKeyRing? = null, // 收件人公钥(用于加密)
+    private var senderPGPSecretKeyRing: PGPSecretKeyRing? = null, // 发件人私钥(用于签名)
+    private val senderPGPSecretKeyPassword: String = "", // 发件人私钥密码
+) {
+
+    private val TAG: String = PgpUtils::class.java.simpleName
+
+    init {
+        Security.addProvider(BouncyCastleProvider())
+    }
+
+    // 发送明文邮件
+    suspend fun sendPlainEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendPlainEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            Transport.send(originalMessage)
+            Pair(true, "Email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send email: ${e.message}")
+        }
+    }
+
+    // 发送签名后的邮件
+    suspend fun sendSignedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendSignedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val secretKeyDecryptor = SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(senderPGPSecretKeyPassword))
+            val producerOptions = ProducerOptions.sign(
+                SigningOptions()
+                    .addInlineSignature(secretKeyDecryptor, senderPGPSecretKeyRing!!, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
+                    .overrideHashAlgorithm(HashAlgorithm.SHA256)
+            ).setAsciiArmor(true)
+            val signedMessage = getEncryptedAndOrSignedMessage(originalMessage, producerOptions)
+            Transport.send(signedMessage)
+            Pair(true, "Email signed and sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to sign and send email: ${e.message}")
+        }
+    }
+
+    // 发送加密邮件
+    suspend fun sendEncryptedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendEncryptedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val producerOptions = ProducerOptions.encrypt(
+                EncryptionOptions.encryptCommunications().addRecipient(recipientPGPPublicKeyRing!!)
+            ).setAsciiArmor(true)
+            val encryptedMessage = getEncryptedAndOrSignedMessage(originalMessage, producerOptions)
+            Transport.send(encryptedMessage)
+            Pair(true, "Encrypted email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send encrypted email: ${e.message}")
+        }
+    }
+
+    // 发送签名加密邮件
+    suspend fun sendSignedAndEncryptedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendSignedAndEncryptedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val secretKeyDecryptor = SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(senderPGPSecretKeyPassword))
+            val producerOptions = ProducerOptions.signAndEncrypt(
+                EncryptionOptions.encryptCommunications().addRecipient(recipientPGPPublicKeyRing!!),
+                SigningOptions()
+                    .addInlineSignature(secretKeyDecryptor, senderPGPSecretKeyRing!!, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
+                    .overrideHashAlgorithm(HashAlgorithm.SHA256)
+            ).setAsciiArmor(true)
+            val encryptedMessage = getEncryptedAndOrSignedMessage(originalMessage, producerOptions)
+            Transport.send(encryptedMessage)
+            Pair(true, "Signed and encrypted email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send signed and encrypted email: ${e.message}")
+        }
+    }
+
+    // 获取原始邮件
+    private fun getOriginalMessage(): MimeMessage {
+        val session = Session.getInstance(properties, authenticator)
+        session.debug = true
+        val message = MimeMessage(session)
+        // 设置直接接收者收件箱
+        val toAddress = toAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.TO, toAddress)
+        // 设置抄送者收件箱
+        val ccAddress = ccAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.CC, ccAddress)
+        // 设置密送者收件箱
+        val bccAddress = bccAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.BCC, bccAddress)
+        // 设置发件箱
+        when {
+            nickname.isEmpty() -> message.setFrom(InternetAddress(from))
+            else -> try {
+                var name = nickname.replace(":", "-").replace("\n", "-")
+                name = MimeUtility.encodeText(name)
+                message.setFrom(InternetAddress("$name <$from>"))
+            } catch (e: Exception) {
+                e.printStackTrace()
+                message.setFrom(InternetAddress(from))
+            }
+        }
+        // 邮件主题
+        try {
+            message.subject = MimeUtility.encodeText(subject.replace(":", "-").replace("\n", "-"))
+        } catch (e: Exception) {
+            e.printStackTrace()
+            message.subject = subject
+        }
+
+        // 邮件内容
+        val contentPart = MimeMultipart("mixed")
+
+        // 邮件正文
+        val textBodyPart = MimeBodyPart()
+        textBodyPart.setContent(body, "text/html;charset=UTF-8")
+        contentPart.addBodyPart(textBodyPart)
+
+        // 邮件附件
+        attachFiles.forEach {
+            val fileBodyPart = MimeBodyPart()
+            val ds = FileDataSource(it)
+            val dh = DataHandler(ds)
+            fileBodyPart.dataHandler = dh
+            fileBodyPart.fileName = MimeUtility.encodeText(dh.name)
+            contentPart.addBodyPart(fileBodyPart)
+        }
+
+        message.setContent(contentPart)
+        message.sentDate = Date()
+        message.saveChanges()
+        return message
+    }
+
+    // 获取加密或且签名邮件: https://datatracker.ietf.org/doc/html/rfc3156#section-4
+    private fun getEncryptedAndOrSignedMessage(originalMessage: MimeMessage, producerOptions: ProducerOptions): MimeMessage {
+        // 将原始消息写入InputStream
+        val baos = ByteArrayOutputStream()
+        originalMessage.writeTo(baos)
+        val inputStream: InputStream = ByteArrayInputStream(baos.toByteArray())
+
+        // 加密数据
+        val outputStream = ByteArrayOutputStream()
+        val encryptionStream = PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(producerOptions)
+        Streams.pipeAll(inputStream, encryptionStream)
+        encryptionStream.close()
+        val result = encryptionStream.result
+        Log.d(TAG, result.toString())
+
+        // The first body part contains the control information necessary to
+        // decrypt the data in the second body part and is labeled according to
+        // the value of the protocol parameter.
+        val versionPart = MimeBodyPart().apply {
+            setText("Version: 1")
+            addHeader("Content-Type", "application/pgp-encrypted")
+            addHeader("Content-Description", "PGP/MIME version identification")
+            //addHeader("Content-Transfer-Encoding", "base64")
+        }
+
+        // The second body part contains the data which was encrypted
+        // and is always labeled application/octet-stream.
+        val encryptedPart = MimeBodyPart().apply {
+            dataHandler = DataHandler(ByteArrayDataSource(outputStream.toByteArray(), "application/octet-stream"))
+            fileName = "encrypted.asc"
+            addHeader("Content-Type", "application/octet-stream; name=\"encrypted.asc\"")
+            addHeader("Content-Description", "OpenPGP encrypted message")
+            addHeader("Content-Disposition", "inline; filename=\"encrypted.asc\"")
+        }
+
+        val encryptedMultiPart = MimeMultipart("encrypted; protocol=\"application/pgp-encrypted\"")
+        encryptedMultiPart.addBodyPart(versionPart, 0)
+        encryptedMultiPart.addBodyPart(encryptedPart, 1)
+
+        val encryptedMessage = MimeMessage(originalMessage.session)
+        encryptedMessage.setRecipients(Message.RecipientType.TO, originalMessage.getRecipients(Message.RecipientType.TO))
+        encryptedMessage.setRecipients(Message.RecipientType.CC, originalMessage.getRecipients(Message.RecipientType.CC))
+        encryptedMessage.setRecipients(Message.RecipientType.BCC, originalMessage.getRecipients(Message.RecipientType.BCC))
+        encryptedMessage.addFrom(originalMessage.from)
+        encryptedMessage.subject = originalMessage.subject
+        encryptedMessage.sentDate = originalMessage.sentDate
+        encryptedMessage.setContent(encryptedMultiPart)
+        encryptedMessage.saveChanges()
+
+        return encryptedMessage
+    }
+
+}
+
diff --git a/app/src/main/java/com/example/firstapp/utils/mail/SmimeUtils.kt b/app/src/main/java/com/example/firstapp/utils/mail/SmimeUtils.kt
new file mode 100644
index 0000000..de148eb
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/mail/SmimeUtils.kt
@@ -0,0 +1,252 @@
+package com.example.firstapp.utils.mail
+
+import com.example.firstapp.utils.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.bouncycastle.cert.jcajce.JcaCertStore
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder
+import org.bouncycastle.cms.CMSAlgorithm
+import org.bouncycastle.cms.CMSEnvelopedDataGenerator
+import org.bouncycastle.cms.CMSProcessableByteArray
+import org.bouncycastle.cms.CMSSignedDataGenerator
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder
+import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder
+import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import org.bouncycastle.operator.OutputEncryptor
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.security.PrivateKey
+import java.security.Security
+import java.security.cert.X509Certificate
+import java.util.Date
+import java.util.Properties
+import javax.activation.DataHandler
+import javax.activation.FileDataSource
+import javax.mail.Authenticator
+import javax.mail.Message
+import javax.mail.Session
+import javax.mail.Transport
+import javax.mail.internet.InternetAddress
+import javax.mail.internet.MimeBodyPart
+import javax.mail.internet.MimeMessage
+import javax.mail.internet.MimeMultipart
+import javax.mail.internet.MimeUtility
+
+@Suppress("PrivatePropertyName")
+class SmimeUtils(
+    private val properties: Properties,
+    private val authenticator: Authenticator,
+    // 邮件参数
+    private val from: String, // 发件人邮箱
+    private val nickname: String, // 发件人昵称
+    private val subject: String, // 邮件主题
+    private val body: String, // 邮件正文
+    private val attachFiles: MutableList<File> = mutableListOf(), // 附件
+    // 收件人参数
+    private val toAddress: MutableList<String> = mutableListOf(), // 收件人邮箱
+    private val ccAddress: MutableList<String> = mutableListOf(), // 抄送者邮箱
+    private val bccAddress: MutableList<String> = mutableListOf(), // 密送者邮箱
+    // 邮件 S/MIME 加密和签名
+    private val recipientX509Cert: X509Certificate? = null, //收件人公钥(用于加密)
+    private val senderPrivateKey: PrivateKey? = null, //发件人私玥(用于签名)
+    private val senderX509Cert: X509Certificate? = null, //发件人公玥(用于签名)
+) {
+
+    private val TAG: String = SmimeUtils::class.java.simpleName
+
+    init {
+        Security.addProvider(BouncyCastleProvider())
+    }
+
+    // 发送明文邮件
+    suspend fun sendPlainEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendPlainEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            Transport.send(originalMessage)
+            Pair(true, "Email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send email: ${e.message}")
+        }
+    }
+
+    // 发送签名后的邮件
+    suspend fun sendSignedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendSignedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val signedMessage = getSignedMessage(originalMessage)
+            Transport.send(signedMessage)
+            Pair(true, "Email signed and sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to sign and send email: ${e.message}")
+        }
+    }
+
+    // 发送加密邮件
+    suspend fun sendEncryptedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendEncryptedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val encryptedMessage = getEncryptedMessage(originalMessage)
+            Transport.send(encryptedMessage)
+            Pair(true, "Encrypted email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send encrypted email: ${e.message}")
+        }
+    }
+
+    // 发送签名加密邮件
+    suspend fun sendSignedAndEncryptedEmail(): Pair<Boolean, String> = withContext(Dispatchers.IO) {
+        Log.d(TAG, "sendSignedAndEncryptedEmail")
+        try {
+            val originalMessage = getOriginalMessage()
+            val signedMessage = getSignedMessage(originalMessage)
+            val encryptedMessage = getEncryptedMessage(signedMessage)
+            Transport.send(encryptedMessage)
+            Pair(true, "Signed and encrypted email sent successfully")
+        } catch (e: Exception) {
+            e.printStackTrace()
+            Pair(false, "Failed to send signed and encrypted email: ${e.message}")
+        }
+    }
+
+    // 获取原始邮件
+    private fun getOriginalMessage(): MimeMessage {
+        val session = Session.getInstance(properties, authenticator)
+        session.debug = true
+        val message = MimeMessage(session)
+        // 设置直接接收者收件箱
+        val toAddress = toAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.TO, toAddress)
+        // 设置抄送者收件箱
+        val ccAddress = ccAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.CC, ccAddress)
+        // 设置密送者收件箱
+        val bccAddress = bccAddress.map { InternetAddress(it) }.toTypedArray()
+        message.setRecipients(Message.RecipientType.BCC, bccAddress)
+        // 设置发件箱
+        when {
+            nickname.isEmpty() -> message.setFrom(InternetAddress(from))
+            else -> try {
+                var name = nickname.replace(":", "-").replace("\n", "-")
+                name = MimeUtility.encodeText(name)
+                message.setFrom(InternetAddress("$name <$from>"))
+            } catch (e: Exception) {
+                e.printStackTrace()
+                message.setFrom(InternetAddress(from))
+            }
+        }
+        // 邮件主题
+        try {
+            message.subject = MimeUtility.encodeText(subject.replace(":", "-").replace("\n", "-"))
+        } catch (e: Exception) {
+            e.printStackTrace()
+            message.subject = subject
+        }
+
+        // 邮件内容
+        val contentPart = MimeMultipart("mixed")
+
+        // 邮件正文
+        val textBodyPart = MimeBodyPart()
+        textBodyPart.setContent(body, "text/html;charset=UTF-8")
+        contentPart.addBodyPart(textBodyPart)
+
+        // 邮件附件
+        attachFiles.forEach {
+            val fileBodyPart = MimeBodyPart()
+            val ds = FileDataSource(it)
+            val dh = DataHandler(ds)
+            fileBodyPart.dataHandler = dh
+            fileBodyPart.fileName = MimeUtility.encodeText(dh.name)
+            contentPart.addBodyPart(fileBodyPart)
+        }
+
+        message.setContent(contentPart)
+        message.sentDate = Date()
+        message.saveChanges()
+        return message
+    }
+
+    // 获取签名邮件
+    private fun getSignedMessage(originalMessage: MimeMessage): MimeMessage {
+        // 创建签名者信息生成器
+        val contentSigner = JcaContentSignerBuilder("SHA256withRSA").build(senderPrivateKey)
+        val certificateHolder = JcaX509CertificateHolder(senderX509Cert)
+        val signerInfoGenerator = JcaSignerInfoGeneratorBuilder(
+            JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider()).build()
+        ).build(contentSigner, certificateHolder)
+
+        // 创建 CMSSignedDataGenerator 并添加签名者信息和证书
+        val generator = CMSSignedDataGenerator()
+        generator.addSignerInfoGenerator(signerInfoGenerator)
+        val certStore = JcaCertStore(listOf(senderX509Cert))
+        generator.addCertificates(certStore)
+
+        // 将邮件内容转换为 CMSSignedData
+        val outputStream = ByteArrayOutputStream()
+        originalMessage.writeTo(outputStream)
+        val contentData = CMSProcessableByteArray(outputStream.toByteArray())
+        val signedData = generator.generate(contentData, true)
+
+        // 创建 MimeMessage 并设置签名后的内容
+        val signedMessage = MimeMessage(originalMessage.session, ByteArrayInputStream(signedData.encoded))
+        /*
+        //TODO: 为什么不需要再设置这些?
+        signedMessage.setRecipients(Message.RecipientType.TO, originalMessage.getRecipients(Message.RecipientType.TO))
+        signedMessage.setRecipients(Message.RecipientType.CC, originalMessage.getRecipients(Message.RecipientType.CC))
+        signedMessage.setRecipients(Message.RecipientType.BCC, originalMessage.getRecipients(Message.RecipientType.BCC))
+        signedMessage.addFrom(originalMessage.from)
+        signedMessage.subject = originalMessage.subject
+        signedMessage.sentDate = originalMessage.sentDate
+        */
+        signedMessage.setContent(signedData.encoded, "application/pkcs7-mime; name=smime.p7m; smime-type=signed-data")
+        signedMessage.saveChanges()
+
+        return signedMessage
+    }
+
+    // 获取加密邮件
+    private fun getEncryptedMessage(originalMessage: MimeMessage): MimeMessage {
+        // 使用收件人的证书进行加密
+        val cmsEnvelopedDataGenerator = CMSEnvelopedDataGenerator()
+        val recipientInfoGenerator = JceKeyTransRecipientInfoGenerator(recipientX509Cert)
+        cmsEnvelopedDataGenerator.addRecipientInfoGenerator(recipientInfoGenerator)
+
+        // 使用 3DES 加密
+        val outputEncryptor: OutputEncryptor = JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).build()
+        val originalContent = ByteArrayOutputStream()
+        originalMessage.writeTo(originalContent)
+        val inputStream = originalContent.toByteArray()
+        val cmsEnvelopedData = cmsEnvelopedDataGenerator.generate(
+            CMSProcessableByteArray(inputStream),
+            outputEncryptor
+        )
+
+        // 创建加密邮件
+        val encryptedMessage = MimeMessage(originalMessage.session)
+        encryptedMessage.setRecipients(Message.RecipientType.TO, originalMessage.getRecipients(Message.RecipientType.TO))
+        encryptedMessage.setRecipients(Message.RecipientType.CC, originalMessage.getRecipients(Message.RecipientType.CC))
+        encryptedMessage.setRecipients(Message.RecipientType.BCC, originalMessage.getRecipients(Message.RecipientType.BCC))
+        encryptedMessage.addFrom(originalMessage.from)
+        encryptedMessage.subject = originalMessage.subject
+        encryptedMessage.sentDate = originalMessage.sentDate
+        encryptedMessage.setContent(cmsEnvelopedData.encoded, "application/pkcs7-mime; name=smime.p7m; smime-type=enveloped-data")
+        encryptedMessage.setHeader("Content-Type", "application/pkcs7-mime; name=smime.p7m; smime-type=enveloped-data")
+        encryptedMessage.setHeader("Content-Disposition", "attachment; filename=smime.p7m")
+        encryptedMessage.setHeader("Content-Description", "S/MIME Encrypted Message")
+        encryptedMessage.addHeader("Content-Transfer-Encoding", "base64")
+        encryptedMessage.saveChanges()
+
+        return encryptedMessage
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/tinker/ShareReflectUtil.kt b/app/src/main/java/com/example/firstapp/utils/tinker/ShareReflectUtil.kt
new file mode 100644
index 0000000..4d05073
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/tinker/ShareReflectUtil.kt
@@ -0,0 +1,240 @@
+package com.example.firstapp.utils.tinker
+
+import android.annotation.SuppressLint
+import android.content.Context
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "UNCHECKED_CAST", "unused")
+object ShareReflectUtil {
+    /**
+     * Locates a given field anywhere in the class inheritance hierarchy.
+     *
+     * @param instance an object to search the field into.
+     * @param name     field name
+     * @return a field object
+     * @throws NoSuchFieldException if the field cannot be located
+     */
+    @Throws(NoSuchFieldException::class)
+    fun findField(instance: Any, name: String): Field {
+        var clazz: Class<*>? = instance.javaClass
+        while (clazz != null) {
+            try {
+                val field = clazz.getDeclaredField(name)
+                if (!field.isAccessible) {
+                    field.isAccessible = true
+                }
+                return field
+            } catch (e: NoSuchFieldException) {
+                // ignore and search next
+            }
+            clazz = clazz.superclass
+        }
+        throw NoSuchFieldException("Field " + name + " not found in " + instance.javaClass)
+    }
+
+    @Throws(NoSuchFieldException::class)
+    fun findField(originClazz: Class<*>, name: String): Field {
+        var clazz: Class<*>? = originClazz
+        while (clazz != null) {
+            try {
+                val field = clazz.getDeclaredField(name)
+                if (!field.isAccessible) {
+                    field.isAccessible = true
+                }
+                return field
+            } catch (e: NoSuchFieldException) {
+                // ignore and search next
+            }
+            clazz = clazz.superclass
+        }
+        throw NoSuchFieldException("Field $name not found in $originClazz")
+    }
+
+    /**
+     * Locates a given method anywhere in the class inheritance hierarchy.
+     *
+     * @param instance       an object to search the method into.
+     * @param name           method name
+     * @param parameterTypes method parameter types
+     * @return a method object
+     * @throws NoSuchMethodException if the method cannot be located
+     */
+    @Throws(NoSuchMethodException::class)
+    fun findMethod(instance: Any, name: String, vararg parameterTypes: Class<*>?): Method {
+        var clazz: Class<*>? = instance.javaClass
+        while (clazz != null) {
+            try {
+                val method = clazz.getDeclaredMethod(name, *parameterTypes)
+                if (!method.isAccessible) {
+                    method.isAccessible = true
+                }
+                return method
+            } catch (e: NoSuchMethodException) {
+                // ignore and search next
+            }
+            clazz = clazz.superclass
+        }
+        throw NoSuchMethodException(
+            "Method "
+                    + name
+                    + " with parameters "
+                    + listOf(*parameterTypes)
+                    + " not found in " + instance.javaClass
+        )
+    }
+
+    /**
+     * Locates a given method anywhere in the class inheritance hierarchy.
+     *
+     * @param clazz          a class to search the method into.
+     * @param name           method name
+     * @param parameterTypes method parameter types
+     * @return a method object
+     * @throws NoSuchMethodException if the method cannot be located
+     */
+    @Throws(NoSuchMethodException::class)
+    fun findMethod(clazz: Class<*>?, name: String, vararg parameterTypes: Class<*>?): Method {
+        var tClazz = clazz
+        while (tClazz != null) {
+            try {
+                val method = tClazz.getDeclaredMethod(name, *parameterTypes)
+                if (!method.isAccessible) {
+                    method.isAccessible = true
+                }
+                return method
+            } catch (e: NoSuchMethodException) {
+                // ignore and search next
+            }
+            tClazz = tClazz.superclass
+        }
+        throw NoSuchMethodException(
+            "Method "
+                    + name
+                    + " with parameters "
+                    + listOf(*parameterTypes)
+                    + " not found in tClazz"
+        )
+    }
+
+    /**
+     * Locates a given constructor anywhere in the class inheritance hierarchy.
+     *
+     * @param instance       an object to search the constructor into.
+     * @param parameterTypes constructor parameter types
+     * @return a constructor object
+     * @throws NoSuchMethodException if the constructor cannot be located
+     */
+    @Throws(NoSuchMethodException::class)
+    fun findConstructor(instance: Any, vararg parameterTypes: Class<*>?): Constructor<*> {
+        var clazz: Class<*>? = instance.javaClass
+        while (clazz != null) {
+            try {
+                val constructor = clazz.getDeclaredConstructor(*parameterTypes)
+                if (!constructor.isAccessible) {
+                    constructor.isAccessible = true
+                }
+                return constructor
+            } catch (e: NoSuchMethodException) {
+                // ignore and search next
+            }
+            clazz = clazz.superclass
+        }
+        throw NoSuchMethodException(
+            "Constructor"
+                    + " with parameters "
+                    + listOf(*parameterTypes)
+                    + " not found in " + instance.javaClass
+        )
+    }
+
+    /**
+     * Replace the value of a field containing a non null array, by a new array containing the
+     * elements of the original array plus the elements of extraElements.
+     *
+     * @param instance      the instance whose field is to be modified.
+     * @param fieldName     the field to modify.
+     * @param extraElements elements to append at the end of the array.
+     */
+    @Throws(NoSuchFieldException::class, IllegalArgumentException::class, IllegalAccessException::class)
+    fun expandFieldArray(instance: Any, fieldName: String, extraElements: Array<Any?>) {
+        val jlrField = findField(instance, fieldName)
+        val original = jlrField[instance] as Array<Any>
+        val combined = java.lang.reflect.Array.newInstance(original.javaClass.componentType, original.size + extraElements.size) as Array<Any>
+
+        // NOTE: changed to copy extraElements first, for patch load first
+        System.arraycopy(extraElements, 0, combined, 0, extraElements.size)
+        System.arraycopy(original, 0, combined, extraElements.size, original.size)
+        jlrField[instance] = combined
+    }
+
+    /**
+     * Replace the value of a field containing a non null array, by a new array containing the
+     * elements of the original array plus the elements of extraElements.
+     *
+     * @param instance      the instance whose field is to be modified.
+     * @param fieldName     the field to modify.
+     */
+    @Throws(NoSuchFieldException::class, IllegalArgumentException::class, IllegalAccessException::class)
+    fun reduceFieldArray(instance: Any, fieldName: String, reduceSize: Int) {
+        if (reduceSize <= 0) {
+            return
+        }
+        val jlrField = findField(instance, fieldName)
+        val original = jlrField[instance] as Array<Any>
+        val finalLength = original.size - reduceSize
+        if (finalLength <= 0) {
+            return
+        }
+        val combined = java.lang.reflect.Array.newInstance(original.javaClass.componentType, finalLength) as Array<Any>
+        System.arraycopy(original, reduceSize, combined, 0, finalLength)
+        jlrField[instance] = combined
+    }
+
+    @SuppressLint("PrivateApi")
+    fun getActivityThread(
+        context: Context?,
+        activityThread: Class<*>?,
+    ): Any? {
+        var tActivityThread = activityThread
+        return try {
+            if (tActivityThread == null) {
+                tActivityThread = Class.forName("android.app.ActivityThread")
+            }
+            val m = tActivityThread!!.getMethod("currentActivityThread")
+            m.isAccessible = true
+            var currentActivityThread = m.invoke(null)
+            if (currentActivityThread == null && context != null) {
+                // In older versions of Android (prior to frameworks/base 66a017b63461a22842)
+                // the currentActivityThread was built on thread locals, so we'll need to try
+                // even harder
+                val mLoadedApk = context.javaClass.getField("mLoadedApk")
+                mLoadedApk.isAccessible = true
+                val apk = mLoadedApk[context]
+                val mActivityThreadField = apk.javaClass.getDeclaredField("mActivityThread")
+                mActivityThreadField.isAccessible = true
+                currentActivityThread = mActivityThreadField[apk]
+            }
+            currentActivityThread
+        } catch (ignore: Throwable) {
+            null
+        }
+    }
+
+    /**
+     * Handy method for fetching hidden integer constant value in system classes.
+     *
+     * @param clazz
+     * @param fieldName
+     * @return
+     */
+    fun getValueOfStaticIntField(clazz: Class<*>, fieldName: String, defVal: Int): Int {
+        return try {
+            val field = findField(clazz, fieldName)
+            field.getInt(null)
+        } catch (thr: Throwable) {
+            defVal
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/utils/tinker/TinkerLoadLibrary.kt b/app/src/main/java/com/example/firstapp/utils/tinker/TinkerLoadLibrary.kt
new file mode 100644
index 0000000..0695dc9
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/utils/tinker/TinkerLoadLibrary.kt
@@ -0,0 +1,196 @@
+/*
+ * Tencent is pleased to support the open source community by making Tinker available.
+ *
+ * Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
+ *
+ * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * https://opensource.org/licenses/BSD-3-Clause
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.firstapp.utils.tinker
+
+import android.annotation.SuppressLint
+import android.os.Build
+import com.example.firstapp.utils.Log
+import java.io.File
+import java.io.IOException
+
+/**
+ * Created by zhangshaowen on 17/1/5.
+ * Thanks for Android Fragmentation
+ */
+@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "UNCHECKED_CAST", "SENSELESS_COMPARISON")
+object TinkerLoadLibrary {
+    private const val TAG = "Tinker.LoadLibrary"
+
+    @SuppressLint("ObsoleteSdkInt")
+    @Throws(Throwable::class)
+    fun installNativeLibraryPath(classLoader: ClassLoader, folder: File?) {
+        if (folder == null || !folder.exists()) {
+            Log.e(TAG, String.format("installNativeLibraryPath, folder %s is illegal", folder))
+            return
+        }
+        // android o sdk_int 26
+        // for android o preview sdk_int 25
+        if (Build.VERSION.SDK_INT == 25 && Build.VERSION.PREVIEW_SDK_INT != 0
+            || Build.VERSION.SDK_INT > 25
+        ) {
+            try {
+                V25.install(classLoader, folder)
+            } catch (throwable: Throwable) {
+                // install fail, try to treat it as v23
+                // some preview N version may go here
+                Log.e(
+                    TAG, String.format(
+                        "installNativeLibraryPath, v25 fail, sdk: %d, error: %s, try to fallback to V23",
+                        Build.VERSION.SDK_INT, throwable.message
+                    )
+                )
+                V23.install(classLoader, folder)
+            }
+        } else if (Build.VERSION.SDK_INT >= 23) {
+            try {
+                V23.install(classLoader, folder)
+            } catch (throwable: Throwable) {
+                // install fail, try to treat it as v14
+                Log.e(
+                    TAG, String.format(
+                        "installNativeLibraryPath, v23 fail, sdk: %d, error: %s, try to fallback to V14",
+                        Build.VERSION.SDK_INT, throwable.message
+                    )
+                )
+                V14.install(classLoader, folder)
+            }
+        } else if (Build.VERSION.SDK_INT >= 14) {
+            V14.install(classLoader, folder)
+        } else {
+            V4.install(classLoader, folder)
+        }
+    }
+
+    object V4 {
+        @Throws(Throwable::class)
+        fun install(classLoader: ClassLoader, folder: File) {
+            val addPath = folder.path
+            val pathField = ShareReflectUtil.findField(classLoader, "libPath")
+            val origLibPaths = pathField[classLoader] as String
+            val origLibPathSplit = origLibPaths.split(":".toRegex()).toTypedArray()
+            val newLibPaths = StringBuilder(addPath)
+            for (origLibPath in origLibPathSplit) {
+                if (origLibPath == null || addPath == origLibPath) {
+                    continue
+                }
+                newLibPaths.append(':').append(origLibPath)
+            }
+            pathField[classLoader] = newLibPaths.toString()
+            val libraryPathElementsFiled = ShareReflectUtil.findField(classLoader, "libraryPathElements")
+            val libraryPathElements = libraryPathElementsFiled[classLoader] as MutableList<String>
+            val libPathElementIt = libraryPathElements.iterator()
+            while (libPathElementIt.hasNext()) {
+                val libPath = libPathElementIt.next()
+                if (addPath == libPath) {
+                    libPathElementIt.remove()
+                    break
+                }
+            }
+            libraryPathElements.add(0, addPath)
+            libraryPathElementsFiled[classLoader] = libraryPathElements
+        }
+    }
+
+    object V14 {
+        @Throws(Throwable::class)
+        fun install(classLoader: ClassLoader, folder: File) {
+            val pathListField = ShareReflectUtil.findField(classLoader, "pathList")
+            val dexPathList = pathListField[classLoader]
+            val nativeLibDirField = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories")
+            val origNativeLibDirs = nativeLibDirField[dexPathList] as Array<File>
+            val newNativeLibDirList: MutableList<File> = ArrayList(origNativeLibDirs.size + 1)
+            newNativeLibDirList.add(folder)
+            for (origNativeLibDir in origNativeLibDirs) {
+                if (folder != origNativeLibDir) {
+                    newNativeLibDirList.add(origNativeLibDir)
+                }
+            }
+            nativeLibDirField[dexPathList] = newNativeLibDirList.toTypedArray()
+        }
+    }
+
+    object V23 {
+        @Throws(Throwable::class)
+        fun install(classLoader: ClassLoader, folder: File) {
+            val pathListField = ShareReflectUtil.findField(classLoader, "pathList")
+            val dexPathList = pathListField[classLoader]
+            val nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories")
+            var origLibDirs = nativeLibraryDirectories[dexPathList] as MutableList<File>
+            if (origLibDirs == null) {
+                origLibDirs = ArrayList(2)
+            }
+            val libDirIt = origLibDirs.iterator()
+            while (libDirIt.hasNext()) {
+                val libDir = libDirIt.next()
+                if (folder == libDir) {
+                    libDirIt.remove()
+                    break
+                }
+            }
+            origLibDirs.add(0, folder)
+            val systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories")
+            var origSystemLibDirs = systemNativeLibraryDirectories[dexPathList] as List<File>
+            if (origSystemLibDirs == null) {
+                origSystemLibDirs = ArrayList(2)
+            }
+            val newLibDirs: MutableList<File> = ArrayList(origLibDirs.size + origSystemLibDirs.size + 1)
+            newLibDirs.addAll(origLibDirs)
+            newLibDirs.addAll(origSystemLibDirs)
+            val makeElements = ShareReflectUtil.findMethod(
+                dexPathList,
+                "makePathElements", MutableList::class.java, File::class.java, MutableList::class.java
+            )
+            val suppressedExceptions = ArrayList<IOException>()
+            val elements = makeElements.invoke(dexPathList, newLibDirs, null, suppressedExceptions) as Array<Any>
+            val nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements")
+            nativeLibraryPathElements[dexPathList] = elements
+        }
+    }
+
+    object V25 {
+        @Throws(Throwable::class)
+        fun install(classLoader: ClassLoader, folder: File) {
+            val pathListField = ShareReflectUtil.findField(classLoader, "pathList")
+            val dexPathList = pathListField[classLoader]
+            val nativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "nativeLibraryDirectories")
+            var origLibDirs = nativeLibraryDirectories[dexPathList] as MutableList<File>
+            if (origLibDirs == null) {
+                origLibDirs = ArrayList(2)
+            }
+            val libDirIt = origLibDirs.iterator()
+            while (libDirIt.hasNext()) {
+                val libDir = libDirIt.next()
+                if (folder == libDir) {
+                    libDirIt.remove()
+                    break
+                }
+            }
+            origLibDirs.add(0, folder)
+            val systemNativeLibraryDirectories = ShareReflectUtil.findField(dexPathList, "systemNativeLibraryDirectories")
+            var origSystemLibDirs = systemNativeLibraryDirectories[dexPathList] as List<File>
+            if (origSystemLibDirs == null) {
+                origSystemLibDirs = ArrayList(2)
+            }
+            val newLibDirs: MutableList<File> = ArrayList(origLibDirs.size + origSystemLibDirs.size + 1)
+            newLibDirs.addAll(origLibDirs)
+            newLibDirs.addAll(origSystemLibDirs)
+            val makeElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", MutableList::class.java)
+            val elements = makeElements.invoke(dexPathList, newLibDirs) as Array<Any>
+            val nativeLibraryPathElements = ShareReflectUtil.findField(dexPathList, "nativeLibraryPathElements")
+            nativeLibraryPathElements[dexPathList] = elements
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_app.xml b/app/src/main/res/drawable/ic_app.xml
new file mode 100644
index 0000000..6e94dfa
--- /dev/null
+++ b/app/src/main/res/drawable/ic_app.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40.0dip"
+    android:height="50.0dip"
+    android:viewportWidth="40.0"
+    android:viewportHeight="50.0">
+    <path
+        android:fillAlpha="0.8"
+        android:fillColor="#BEC2C7"
+        android:fillType="evenOdd"
+        android:pathData="M24.6702,0C26.9147,0 29.0561,0.9429 30.5715,2.5987L37.9014,10.6071C39.2513,12.0821 40,14.009 40,16.0084V44C40,47.3137 37.3137,50 34,50H6C2.6863,50 0,47.3137 0,44V6C0,2.6863 2.6863,0 6,0H24.6702Z"
+        android:strokeAlpha="0.8" />
+    <group
+        android:scaleX="2"
+        android:scaleY="2"
+        android:translateX="-4.2"
+        android:translateY="2">
+    <path
+        android:fillColor="#ffffffff"
+        android:fillType="evenOdd"
+        android:pathData="M7.984,5.837C8.418,5.586 8.974,5.735 9.224,6.169L9.779,7.13C10.518,6.712 11.372,6.473 12.282,6.473C13.199,6.473 14.06,6.715 14.803,7.14L15.342,6.207C15.592,5.772 16.148,5.623 16.582,5.874C17.017,6.125 17.165,6.68 16.915,7.115L16.212,8.331C16.862,9.121 17.278,10.11 17.355,11.194H7.21C7.287,10.103 7.708,9.107 8.366,8.314L7.651,7.077C7.401,6.643 7.55,6.087 7.984,5.837ZM17.368,12.647H7.198V17.732C7.198,18.535 7.848,19.185 8.651,19.185H15.915C16.718,19.185 17.368,18.535 17.368,17.732V12.647Z" />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
new file mode 100644
index 0000000..46fc8de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml
new file mode 100644
index 0000000..f8bb0b5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_home_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/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>
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
new file mode 100644
index 0000000..78b75c3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sim.xml b/app/src/main/res/drawable/ic_sim.xml
new file mode 100644
index 0000000..04e7cd5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sim.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40.0dip"
+    android:height="50.0dip"
+    android:viewportWidth="40.0"
+    android:viewportHeight="50.0">
+    <path
+        android:fillAlpha="0.8"
+        android:fillColor="#BEC2C7"
+        android:fillType="evenOdd"
+        android:pathData="M24.6702,0C26.9147,0 29.0561,0.9429 30.5715,2.5987L37.9014,10.6071C39.2513,12.0821 40,14.009 40,16.0084V44C40,47.3137 37.3137,50 34,50H6C2.6863,50 0,47.3137 0,44V6C0,2.6863 2.6863,0 6,0H24.6702Z
+        M18,43L14,43v-4h4v4z
+        M34,43h-4v-4h4v4z
+        M18,35L14,35v-8h4v8z
+        M26,43h-4v-8h4v8z
+        M26,31h-4v-4h4v4z
+        M34,35h-4v-8h4v8z"
+        android:strokeAlpha="0.8" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sim1.xml b/app/src/main/res/drawable/ic_sim1.xml
new file mode 100644
index 0000000..a7b33bf
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sim1.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40.0dip"
+    android:height="50.0dip"
+    android:viewportWidth="40.0"
+    android:viewportHeight="50.0">
+    <path
+        android:fillAlpha="0.8"
+        android:fillColor="#BEC2C7"
+        android:fillType="evenOdd"
+        android:pathData="M24.6702,0C26.9147,0 29.0561,0.9429 30.5715,2.5987L37.9014,10.6071C39.2513,12.0821 40,14.009 40,16.0084V44C40,47.3137 37.3137,50 34,50H6C2.6863,50 0,47.3137 0,44V6C0,2.6863 2.6863,0 6,0H24.6702ZM22.785,13.8333H18.8717L13.33,15.4483V20.3117L17.7633,19.045V38H22.785V13.8333Z"
+        android:strokeAlpha="0.8" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sim2.xml b/app/src/main/res/drawable/ic_sim2.xml
new file mode 100644
index 0000000..a2c81d6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sim2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40.0dip"
+    android:height="50.0dip"
+    android:viewportWidth="40.0"
+    android:viewportHeight="50.0">
+    <path
+        android:fillAlpha="0.8"
+        android:fillColor="#BEC2C7"
+        android:fillType="evenOdd"
+        android:pathData="M30.5715,2.5987C29.0561,0.9429 26.9147,0 24.6702,0H6C2.6863,0 0,2.6863 0,6V44C0,47.3137 2.6863,50 6,50H34C37.3137,50 40,47.3137 40,44V16.0084C40,14.009 39.2513,12.0821 37.9014,10.6071L30.5715,2.5987ZM20.1471,13C17.7983,13 15.7604,13.7514 14.3071,15.1044C12.8518,16.4593 12,18.4019 12,20.7467V20.9918H16.7129V20.7467C16.7129,19.6857 17.0933,18.8354 17.7036,18.2496C18.3155,17.6623 19.1728,17.3274 20.1471,17.3274C22.0709,17.3274 23.3606,18.4956 23.3606,20.0899C23.3606,21.317 22.9263,22.2977 21.5103,23.9022L21.5087,23.9041L12.3677,34.4825V38H27.7794V33.7763H18.6271L24.6667,26.89C26.6563,24.638 28,22.6766 28,20.0899C28,16.0219 24.5918,13 20.1471,13Z"
+        android:strokeAlpha="0.8" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_sms.xml b/app/src/main/res/drawable/ic_sms.xml
new file mode 100644
index 0000000..2d12564
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sms.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="#BEC2C7"
+        android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM9,11L7,11L7,9h2v2zM13,11h-2L11,9h2v2zM17,11h-2L15,9h2v2z" />
+</vector>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..06ea6ca
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="?attr/actionBarSize">
+
+    <com.google.android.material.bottomnavigation.BottomNavigationView
+        android:id="@+id/nav_view"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="0dp"
+        android:layout_marginEnd="0dp"
+        android:background="?android:attr/windowBackground"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:menu="@menu/bottom_nav_menu" />
+
+    <fragment
+        android:id="@+id/nav_host_fragment_activity_main"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:layout_constraintBottom_toTopOf="@id/nav_view"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navGraph="@navigation/mobile_navigation" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
new file mode 100644
index 0000000..166ab0e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".ui.dashboard.DashboardFragment">
+
+    <TextView
+        android:id="@+id/text_dashboard"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textSize="20sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
new file mode 100644
index 0000000..0a6dc93
--- /dev/null
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".ui.home.HomeFragment">
+
+<!--    <TextView-->
+<!--        android:id="@+id/text_home"-->
+<!--        android:layout_width="match_parent"-->
+<!--        android:layout_height="wrap_content"-->
+<!--        android:layout_marginStart="8dp"-->
+<!--        android:layout_marginTop="8dp"-->
+<!--        android:layout_marginEnd="8dp"-->
+<!--        android:textAlignment="center"-->
+<!--        android:textSize="20sp"-->
+<!--        app:layout_constraintBottom_toBottomOf="parent"-->
+<!--        app:layout_constraintEnd_toEndOf="parent"-->
+<!--        app:layout_constraintStart_toStartOf="parent"-->
+<!--        app:layout_constraintTop_toTopOf="parent" />-->
+
+    <androidx.recyclerview.widget.RecyclerView
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/recyclerView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="8dp"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_notifications.xml b/app/src/main/res/layout/fragment_notifications.xml
new file mode 100644
index 0000000..d417935
--- /dev/null
+++ b/app/src/main/res/layout/fragment_notifications.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.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=".ui.notifications.NotificationsFragment">
+
+    <TextView
+        android:id="@+id/text_notifications"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:textAlignment="center"
+        android:textSize="20sp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_layout.xml b/app/src/main/res/layout/item_layout.xml
new file mode 100644
index 0000000..22792ce
--- /dev/null
+++ b/app/src/main/res/layout/item_layout.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:padding="16dp">
+
+    <TextView
+        android:id="@+id/tvTitle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:textStyle="bold"/>
+
+    <TextView
+        android:id="@+id/tvDescription"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="14sp"
+        android:layout_marginTop="4dp"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/sms_code.xml b/app/src/main/res/layout/sms_code.xml
new file mode 100644
index 0000000..b070112
--- /dev/null
+++ b/app/src/main/res/layout/sms_code.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+<!--    线性布局-->
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..fb6d040
--- /dev/null
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item
+        android:id="@+id/navigation_home"
+        android:icon="@drawable/ic_home_black_24dp"
+        android:title="@string/title_home" />
+
+    <item
+        android:id="@+id/navigation_dashboard"
+        android:icon="@drawable/ic_dashboard_black_24dp"
+        android:title="@string/title_dashboard" />
+
+    <item
+        android:id="@+id/navigation_notifications"
+        android:icon="@drawable/ic_notifications_black_24dp"
+        android:title="@string/title_notifications" />
+
+</menu>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?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" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?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" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
new file mode 100644
index 0000000..9a3d6a7
--- /dev/null
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation 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:id="@+id/mobile_navigation"
+    app:startDestination="@+id/navigation_home">
+
+    <fragment
+        android:id="@+id/navigation_home"
+        android:name="com.example.firstapp.ui.home.HomeFragment"
+        android:label="@string/title_home"
+        tools:layout="@layout/fragment_home" />
+
+    <fragment
+        android:id="@+id/navigation_dashboard"
+        android:name="com.example.firstapp.ui.dashboard.DashboardFragment"
+        android:label="@string/title_dashboard"
+        tools:layout="@layout/fragment_dashboard" />
+
+    <fragment
+        android:id="@+id/navigation_notifications"
+        android:name="com.example.firstapp.ui.notifications.NotificationsFragment"
+        android:label="@string/title_notifications"
+        tools:layout="@layout/fragment_notifications" />
+</navigation>
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..64e57ea
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.FirstApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e00c2dd
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..6776d63
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+<resources>
+    <string name="app_name">FirstApp</string>
+    <string name="title_home">Home</string>
+    <string name="title_dashboard">Dashboard</string>
+    <string name="title_notifications">Notifications</string>
+
+
+    <string name="notification_content">短信监听小程序</string>
+
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..1fe83df
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.FirstApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/app/src/test/java/com/example/firstapp/ExampleUnitTest.kt b/app/src/test/java/com/example/firstapp/ExampleUnitTest.kt
new file mode 100644
index 0000000..ced19ac
--- /dev/null
+++ b/app/src/test/java/com/example/firstapp/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.firstapp
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}
\ No newline at end of file
diff --git a/app/x-library.gradle b/app/x-library.gradle
new file mode 100644
index 0000000..de42c83
--- /dev/null
+++ b/app/x-library.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.xuexiang.xrouter'
+apply plugin: 'kotlin-kapt'
+//apply plugin: 'android-aspectjx'
+apply plugin: 'com.xuexiang.xaop'
+
+//自动添加依赖
+configurations.each { configuration ->
+    def dependencies = getProject().dependencies
+    if (configuration.name == "implementation") {
+        //为Project加入X-Library依赖
+        //XUI框架
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xui))
+        configuration.dependencies.add(dependencies.create(deps.androidx.appcompat))
+        configuration.dependencies.add(dependencies.create(deps.androidx.recyclerview))
+        configuration.dependencies.add(dependencies.create(deps.androidx.design))
+        configuration.dependencies.add(dependencies.create(deps.glide))
+        //XUtil工具类
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xutil_core))
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xutil_sub))
+        //XAOP切片
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xaop_runtime))
+        //XUpdate版本更新
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xupdate))
+        //XHttp2
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xhttp2))
+        configuration.dependencies.add(dependencies.create(deps.rxjava2))
+        configuration.dependencies.add(dependencies.create(deps.rxandroid))
+        configuration.dependencies.add(dependencies.create(deps.okhttp3))
+        configuration.dependencies.add(dependencies.create(deps.gson))
+        //XPage
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xpage_lib))
+        //页面路由
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xrouter_runtime))
+    }
+
+    if (configuration.name == "kapt") {
+        //XPage
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xpage_compiler))
+        //页面路由
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xrouter_compiler))
+    }
+
+    if (isNeedLeakcanary.toBoolean() && configuration.name == "debugImplementation") {
+        //内存泄漏监测leak
+        configuration.dependencies.add(dependencies.create(deps.leakcanary))
+    }
+}
+
+configurations.configureEach {
+    resolutionStrategy.force deps.okhttp3
+    //总是拉取最新的 build 版本
+    resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
+}
+
+/*
+aspectjx {
+    // 这里需要修改包名
+    include 'com.example.firstapp'
+    // 排除所有package路径中包含`android.support`的class文件及库(jar文件)
+    exclude 'android.support'
+    // 移除kotlin相关,编译错误和提升速度
+    exclude 'kotlin.jvm', 'kotlin.internal'
+    exclude 'kotlinx.coroutines.internal', 'kotlinx.coroutines.android'
+    exclude '*.jar', '*.aar', '*.so'
+}
+*/
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..c47d555
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,45 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+    apply from: './versions.gradle'
+    addRepos(repositories) //增加代码仓库
+    dependencies {
+        classpath deps.android_gradle_plugin
+        classpath deps.android_maven_gradle_plugin
+        //图片压缩
+        classpath 'com.chenenyu:img-optimizer:1.3.0'
+        //美团多渠道打包
+        //classpath 'com.meituan.android.walle:plugin:1.1.6'
+        //滴滴的质量优化框架
+        if (isNeedPackage.toBoolean() && isUseBooster.toBoolean()) {
+            classpath deps.booster.gradle_plugin
+            classpath deps.booster.task_processed_res
+            classpath deps.booster.task_resource_deredundancy
+        }
+        //AndServer
+        classpath 'cn.ppps.andserver:plugin:2.1.12'
+    }
+}
+
+plugins {
+    alias(libs.plugins.android.application) apply false
+    alias(libs.plugins.jetbrains.kotlin.android) apply false
+}
+
+
+
+//allprojects {
+//    repositories {
+////        google()
+////        mavenCentral()
+////        jcenter()
+////        maven { url 'https://maven.aliyun.com/repository/google' }
+////        maven { url 'https://maven.aliyun.com/repository/central' }
+////        maven { url 'https://maven.aliyun.com/repository/public' }
+////        maven { url 'https://repo1.maven.org/maven2/' }
+////        maven { url 'https://oss.sonatype.org/content/repositories/public' }
+////        maven { url "https://jitpack.io" }
+//    }
+//
+//    // 将构建文件统一输出到项目根目录下的 build 文件夹
+//    setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}"))
+//}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..15a3aae
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,39 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+#org.gradle.jvmargs=-Xmx1024m -Dfile.encoding=UTF-8
+org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
+                   --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
+                   --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+
+# ?? KAPT ?? JDK ????
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
+
+isNeedPackage=true
+isNeedClean=true
+excludeFrpclib=true
+isNeedLeakcanary=false
+isUseBooster=false
+android.precompileDependenciesResources=false
+android.enableJetifier=true
+#android.enableD8=true
+org.gradle.java.home=D\:\\tanjiyaun\\jdk\\jdk1.8\\jdk-17.0.12
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..8108deb
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,32 @@
+[versions]
+agp = "8.5.1"
+kotlin = "1.9.0"
+coreKtx = "1.10.1"
+junit = "4.13.2"
+junitVersion = "1.1.5"
+espressoCore = "3.5.1"
+appcompat = "1.6.1"
+material = "1.10.0"
+constraintlayout = "2.1.4"
+lifecycleLivedataKtx = "2.6.1"
+lifecycleViewmodelKtx = "2.6.1"
+navigationFragmentKtx = "2.6.0"
+navigationUiKtx = "2.6.0"
+
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
+androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
+androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
+androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a94a9b8
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,8 @@
+#Thu Jan 16 11:18:21 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+#distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.7-bin.zip
+#distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..f09bb6b
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,39 @@
+pluginManagement {
+    repositories {
+        google {
+            content {
+                includeGroupByRegex("com\\.android.*")
+                includeGroupByRegex("com\\.google.*")
+                includeGroupByRegex("androidx.*")
+            }
+        }
+        mavenCentral()
+        gradlePluginPortal()
+        maven { url "https://jitpack.io" }
+        google()
+        jcenter()
+        maven { url 'https://maven.aliyun.com/repository/google' }
+        maven { url 'https://maven.aliyun.com/repository/central' }
+        maven { url 'https://maven.aliyun.com/repository/public' }
+        maven { url 'https://repo1.maven.org/maven2/' }
+        maven { url 'https://oss.sonatype.org/content/repositories/public' }
+
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+        maven { url "https://jitpack.io" }
+        jcenter()
+        maven { url 'https://maven.aliyun.com/repository/google' }
+        maven { url 'https://maven.aliyun.com/repository/central' }
+        maven { url 'https://maven.aliyun.com/repository/public' }
+        maven { url 'https://repo1.maven.org/maven2/' }
+        maven { url 'https://oss.sonatype.org/content/repositories/public' }
+    }
+}
+
+rootProject.name = "FirstApp"
+include ':app'
diff --git a/versions.gradle b/versions.gradle
new file mode 100644
index 0000000..8e3c229
--- /dev/null
+++ b/versions.gradle
@@ -0,0 +1,223 @@
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+
+def build_versions = [:]
+build_versions.version_code = 53
+build_versions.version_name = "3.3.2"
+build_versions.min_sdk = 19
+build_versions.target_sdk = 33
+build_versions.build_tools = "33.0.1"
+ext.build_versions = build_versions
+
+ext.deps = [:]
+def versions = [:]
+versions.android_gradle_plugin = '7.2.2'
+versions.android_maven_gradle_plugin = "2.1"
+versions.gradle_bintray_plugin = "1.8.0"
+versions.booster = "3.1.0"
+versions.booster_all = "1.1.1"
+versions.support = "28.0.0"
+versions.annotation = "1.3.0"
+versions.androidx = "1.4.1"
+versions.recyclerview = "1.2.1"
+versions.material = "1.5.0"
+versions.junit = "4.13.2"
+versions.espresso = '3.5.1'
+versions.constraint_layout = "2.1.3"
+versions.glide = "4.13.1"
+versions.rxjava2 = "2.2.21"
+versions.rxandroid = "2.1.1"
+versions.rxbinding = "2.2.0"
+versions.butterknife = "10.2.3"
+versions.runner = "1.4.0"
+versions.gson = "2.10.1" //https://github.com/google/gson
+versions.okhttp3 = "3.12.13" //不可升级,为了支持 API 19
+versions.leakcanary = "2.13" //https://github.com/square/leakcanary
+versions.lifecycle = "2.2.0"
+//versions.kotlin = '1.7.21'
+versions.kotlin = '1.6.21'
+
+//========xlibrary start========//
+
+versions.xui = "dev~1.2.2-SNAPSHOT"
+versions.xupdate = "2.1.5"
+versions.xaop = "dab6b77b2f" //1.1.0
+versions.xutil = "2.0.0"
+versions.xhttp2 = "9115cfcd5a" //2.0.9
+versions.xpage = "3.3.0"
+versions.xrouter = "1.0.1"
+
+//========xlibrary end========//
+
+ext.versions = versions
+
+def deps = [:]
+
+def support = [:]
+support.annotations = "com.android.support:support-annotations:$versions.support"
+support.app_compat = "com.android.support:appcompat-v7:$versions.support"
+support.recyclerview = "com.android.support:recyclerview-v7:$versions.support"
+support.cardview = "com.android.support:cardview-v7:$versions.support"
+support.design = "com.android.support:design:$versions.support"
+support.v4 = "com.android.support:support-v4:$versions.support"
+support.core_utils = "com.android.support:support-core-utils:$versions.support"
+deps.support = support
+
+def androidx = [:]
+androidx.annotations = "androidx.annotation:annotation:$versions.annotation"
+androidx.appcompat = "androidx.appcompat:appcompat:$versions.androidx"
+androidx.recyclerview = "androidx.recyclerview:recyclerview:$versions.recyclerview"
+androidx.design = "com.google.android.material:material:$versions.material"
+androidx.multidex = 'androidx.multidex:multidex:2.0.1'
+deps.androidx = androidx
+
+def booster = [:]
+booster.gradle_plugin = "com.didiglobal.booster:booster-gradle-plugin:$versions.booster"
+booster.task_all = "com.didiglobal.booster:booster-task-all:$versions.booster_all"
+booster.transform_all = "com.didiglobal.booster:booster-transform-all:$versions.booster_all"
+//采用 cwebp 对资源进行压缩
+booster.task_compression_cwebp = "com.didiglobal.booster:booster-task-compression-cwebp:$versions.booster"
+//采用 pngquant 对资源进行压缩
+booster.task_compression_pngquant = "com.didiglobal.booster:booster-task-compression-pngquant:$versions.booster"
+//ap_ 文件压缩
+booster.task_processed_res = "com.didiglobal.booster:booster-task-compression-processed-res:$versions.booster"
+//去冗余资源
+booster.task_resource_deredundancy = "com.didiglobal.booster:booster-task-resource-deredundancy:$versions.booster"
+//检查 SNAPSHOT 版本
+booster.task_check_snapshot = "com.didiglobal.booster:booster-task-check-snapshot:$versions.booster"
+//性能瓶颈检测
+booster.transform_lint = "com.didiglobal.booster:booster-transform-lint:$versions.booster"
+//多线程优化
+booster.transform_thread = "com.didiglobal.booster:booster-transform-thread:$versions.booster"
+//资源索引内联
+booster.transform_r_inline = "com.didiglobal.booster:booster-transform-r-inline:$versions.booster"
+//WebView 预加载
+booster.transform_webview = "com.didiglobal.booster:booster-transform-webview:$versions.booster"
+//SharedPreferences 优化
+booster.transform_shared_preferences = "com.didiglobal.booster:booster-transform-shared-preferences:$versions.booster"
+//检查覆盖安装导致的 Resources 和 Assets 未加载的 Bug
+booster.transform_res_check = "com.didiglobal.booster:booster-transform-res-check:$versions.booster"
+//修复 Toast 在 Android 7.1 上的 Bug
+booster.transform_toast = "com.didiglobal.booster:booster-transform-toast:$versions.booster"
+//处理系统 Crash
+booster.transform_activity_thread = "com.didiglobal.booster:booster-transform-activity-thread:$versions.booster"
+deps.booster = booster
+
+def butterknife = [:]
+butterknife.runtime = "com.jakewharton:butterknife:$versions.butterknife"
+butterknife.compiler = "com.jakewharton:butterknife-compiler:$versions.butterknife"
+
+deps.butterknife = butterknife
+
+def espresso = [:]
+espresso.core = "androidx.test.espresso:espresso-core:$versions.espresso"
+espresso.contrib = "androidx.test.espresso:espresso-contrib:$versions.espresso"
+espresso.intents = "androidx.test.espresso:espresso-intents:$versions.espresso"
+deps.espresso = espresso
+
+deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin"
+deps.android_maven_gradle_plugin = "com.github.dcendents:android-maven-gradle-plugin:$versions.android_maven_gradle_plugin"
+deps.gradle_bintray_plugin = "com.jfrog.bintray.gradle:gradle-bintray-plugin:$versions.gradle_bintray_plugin"
+deps.glide = "com.github.bumptech.glide:glide:$versions.glide"
+deps.constraint_layout = "androidx.constraint:constraint-layout:$versions.constraint_layout"
+deps.junit = "junit:junit:$versions.junit"
+deps.runner = "androidx.test:runner:$versions.runner"
+deps.rxjava2 = "io.reactivex.rxjava2:rxjava:$versions.rxjava2"
+deps.rxandroid = "io.reactivex.rxjava2:rxandroid:$versions.rxandroid"
+deps.rxbinding = "com.jakewharton.rxbinding2:rxbinding:$versions.rxbinding"
+deps.gson = "com.google.code.gson:gson:$versions.gson"
+deps.okhttp3 = "com.squareup.okhttp3:okhttp:$versions.okhttp3"
+deps.leakcanary = "com.squareup.leakcanary:leakcanary-android:$versions.leakcanary"
+
+//========xlibrary start=================//
+
+def xlibrary = [:]
+
+xlibrary.xui = "com.github.pppscn:XUI:$versions.xui" //com.github.xuexiangjys:XUI
+xlibrary.xupdate = "com.github.xuexiangjys:XUpdate:$versions.xupdate"
+xlibrary.xaop_runtime = "com.github.pppscn.XAOP:xaop-runtime:$versions.xaop" //com.github.xuexiangjys.XAOP
+xlibrary.xaop_plugin = "com.github.pppscn.XAOP:xaop-plugin:$versions.xaop" //com.github.xuexiangjys.XAOP
+xlibrary.xutil_core = "com.github.xuexiangjys.XUtil:xutil-core:$versions.xutil"
+xlibrary.xutil_sub = "com.github.xuexiangjys.XUtil:xutil-sub:$versions.xutil"
+xlibrary.xhttp2 = "com.github.pppscn:XHttp2:$versions.xhttp2" //com.github.xuexiangjys:XHttp2
+xlibrary.xpage_lib = "com.github.xuexiangjys.XPage:xpage-lib:$versions.xpage"
+xlibrary.xpage_compiler = "com.github.xuexiangjys.XPage:xpage-compiler:$versions.xpage"
+xlibrary.xrouter_runtime = "com.github.xuexiangjys.XRouter:xrouter-runtime:$versions.xrouter"
+xlibrary.xrouter_compiler = "com.github.xuexiangjys.XRouter:xrouter-compiler:$versions.xrouter"
+xlibrary.xrouter_plugin = "com.github.xuexiangjys.XRouter:xrouter-plugin:$versions.xrouter"
+
+deps.xlibrary = xlibrary
+
+//========xlibrary end=================//
+
+def lifecycle = [:]
+lifecycle.runtime = "androidx.lifecycle:lifecycle-runtime:$versions.lifecycle"
+lifecycle.java8 = "androidx.lifecycle:lifecycle-common-java8:$versions.lifecycle"
+lifecycle.compiler = "androidx.lifecycle:lifecycle-compiler:$versions.lifecycle"
+lifecycle.viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.lifecycle"
+lifecycle.livedata_ktx = "androidx.lifecycle:lifecycle-livedata-ktx:$versions.lifecycle"
+deps.lifecycle = lifecycle
+
+ext.deps = deps
+
+/**
+ * @return 是否为release
+ */
+def isRelease() {
+    Gradle gradle = getGradle()
+    String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
+
+    Pattern pattern
+    if (tskReqStr.contains("assemble")) {
+        println tskReqStr
+        pattern = Pattern.compile("assemble(\\w*)(Release|Debug)")
+    } else {
+        pattern = Pattern.compile("generate(\\w*)(Release|Debug)")
+    }
+    Matcher matcher = pattern.matcher(tskReqStr)
+
+    if (matcher.find()) {
+        String task = matcher.group(0).toLowerCase()
+        println("[BuildType] Current task: " + task)
+        return task.contains("release")
+    } else {
+        println "[BuildType] NO MATCH FOUND"
+        return true
+    }
+}
+
+ext.isRelease = this.&isRelease
+
+//默认添加代码仓库路径
+static def addRepos(RepositoryHandler handler) {
+    handler.mavenLocal()
+    //handler.google { url 'https://maven.aliyun.com/repository/google' }
+    //handler.mavenCentral { url 'https://maven.aliyun.com/repository/central' }
+    handler.google()
+    handler.mavenCentral()
+    handler.maven { url 'https://maven.aliyun.com/repository/google' }
+    handler.maven { url 'https://maven.aliyun.com/repository/central' }
+    handler.maven { url 'https://maven.aliyun.com/repository/public' }
+    handler.maven { url "https://repo1.maven.org/maven2/" }
+    handler.maven { url 'https://oss.sonatype.org/content/repositories/public' }
+    handler.maven { url "https://jitpack.io" }
+    //Add the Local repository
+    handler.maven { url 'LocalRepository' }
+}
+
+ext.addRepos = this.&addRepos
+
+//自动添加XAOP和XRouter插件
+project.buildscript.configurations.each { configuration ->
+    def dependencies = getProject().dependencies
+    if (configuration.name == "classpath") {
+        //XAOP插件
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xaop_plugin))
+        //XRouter插件
+        configuration.dependencies.add(dependencies.create(deps.xlibrary.xrouter_plugin))
+        //AspectJX
+        //configuration.dependencies.add(dependencies.create('com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'))
+        //kotlin插件
+        configuration.dependencies.add(dependencies.create("org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"))
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.3