cloudroam
2025-02-14 2fce91b8c0faf1290d8a35ee022dab3cdbc28a54
init
已修改1个文件
已添加119个文件
7211 ■■■■■ 文件已修改
.gitignore 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/.name 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/codeStyles/Project.xml 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/codeStyles/codeStyleConfig.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/compiler.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/deploymentTargetSelector.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/gradle.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/kotlinc.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/migrations.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/misc.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.idea/runConfigurations.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/.gitignore 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/.idea/.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/.idea/gradle.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/.idea/misc.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/build.gradle 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/libs/frpclib-sources.jar 补丁 | 查看 | 原始文档 | blame | 历史
app/libs/frpclib.aar 补丁 | 查看 | 原始文档 | blame | 历史
app/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/androidTest/java/com/example/firstapp/ExampleInstrumentedTest.kt 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/AndroidManifest.xml 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/App.kt 455 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/MainActivity.kt 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter.kt 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter2.kt 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/MyAdapter_bak.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/core/Core.kt 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/AppDatabase.kt 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/AppDatabase_bak.kt 442 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/dao/MsgDao.kt 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/Code.kt 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/entity/Msg.kt 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/ext/ConvertersDate.kt 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/repository/MsgRepository.kt 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/entity/Item.kt 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/entity/Rule.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/entity/SmsInfo.kt 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/CactusReceiver.kt 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/receiver/SmsReceiver.kt 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/service/BluetoothScanService.kt 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/service/HttpServerService.kt 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeFragment.kt 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsFragment.kt 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/notifications/NotificationsViewModel.kt 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/AppUtils.kt 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/Base64.kt 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/BluetoothUtils.kt 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/CacheUtils.kt 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/CactusSave.kt 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/Constants.kt 318 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/DrawableUtils.kt 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/FrpcUtils.kt 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/HistoryUtils.kt 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/KeepAliveUtils.kt 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/LocationUtils.kt 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/Log.kt 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/RSACrypt.kt 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/RandomUtils.kt 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/SM4Crypt.kt 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/SettingUtils.kt 166 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/SharedPreference.kt 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/mail/EmailSender.kt 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/mail/PgpUtils.kt 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/mail/SmimeUtils.kt 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/tinker/ShareReflectUtil.kt 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/utils/tinker/TinkerLoadLibrary.kt 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_app.xml 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_dashboard_black_24dp.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_home_black_24dp.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_launcher_background.xml 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_launcher_foreground.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_notifications_black_24dp.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_sim.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_sim1.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_sim2.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/drawable/ic_sms.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/activity_main.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_dashboard.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_home.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/fragment_notifications.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/item_layout.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/layout/sms_code.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/menu/bottom_nav_menu.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-hdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-mdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/navigation/mobile_navigation.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values-night/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/colors.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/dimens.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/strings.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/values/themes.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/backup_rules.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/res/xml/data_extraction_rules.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/test/java/com/example/firstapp/ExampleUnitTest.kt 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/x-library.gradle 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
build.gradle 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle.properties 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle/libs.versions.toml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradle/wrapper/gradle-wrapper.jar 补丁 | 查看 | 原始文档 | blame | 历史
gradle/wrapper/gradle-wrapper.properties 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradlew 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
gradlew.bat 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
settings.gradle 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
versions.gradle 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.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
.idea/.gitignore
对比新文件
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
.idea/.name
对比新文件
@@ -0,0 +1 @@
FirstApp
.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>
.idea/codeStyles/codeStyleConfig.xml
对比新文件
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
  <state>
    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
  </state>
</component>
.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>
.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>
.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>
.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>
.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>
.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>
.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>
app/.gitignore
对比新文件
@@ -0,0 +1 @@
/build
app/.idea/.gitignore
对比新文件
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
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>
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>
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")
}
app/libs/frpclib-sources.jar
Binary files differ
app/libs/frpclib.aar
Binary files differ
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
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)
    }
}
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>
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",
//            )
//        )
//    }
}
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)
    }
}
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
        }
    }
}
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
}
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
}
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()
    }
}
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)")
            }
        }
    }
}
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
            }
        }
    }
}
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>
}
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>
}
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(),
)
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
            }
        }
}
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
    }
}
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()
}
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)
}
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
)
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
    }
}
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
            }
        }
}
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)
                }
            }
        }
    }
}
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: 没有匹配到内容")
                    }
                }
            }
        }
    }
}
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()
    }
}
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: ")
    }
}
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
    }
}
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
}
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
    }
}
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() // 重新获取并更新数据
    }
}
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
    }
}
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
}
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)
        }
    }*/
}
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"))
    }
}
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)
    }
}
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"
        }
    }
}
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")
}
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}}")
)
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.")
    }
}
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()
            }
        }
    }
}
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
    }
}
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)
            }
        }
    }
}
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
    }
}
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)
    }
}
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)))
    }
}
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!")
    }
}
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)
        }
    }
}
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...")
    }
}
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()
    }
}
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)
        }
    }
}
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
    }
}
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
    }
}
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
        }
    }
}
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
        }
    }
}
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
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>
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>
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>
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>
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>
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>
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>
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>
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)
    }
}
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'
}
*/
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(':', '/')}"))
//}
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
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" }
gradle/wrapper/gradle-wrapper.jar
Binary files differ
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
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" "$@"
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
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'
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"))
    }
}