├── SCOPE ├── SUMMARY ├── SOURCE_URL ├── code ├── app │ ├── src │ │ ├── main │ │ │ ├── assets │ │ │ │ ├── xposed_init │ │ │ │ └── easygo.json │ │ │ ├── res │ │ │ │ ├── values-w1240dp │ │ │ │ │ └── dimens.xml │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── off.png │ │ │ │ │ ├── snhj.png │ │ │ │ │ ├── ysxb.png │ │ │ │ │ ├── caixing.png │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ ├── ic_menu_send.png │ │ │ │ │ ├── notepaad_win11.png │ │ │ │ │ ├── sunglass_huaji.png │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ └── ic_lockscreen_answer_active.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── jinfan.png │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ └── ic_launcher_round.webp │ │ │ │ ├── values-land │ │ │ │ │ └── dimens.xml │ │ │ │ ├── values-w600dp │ │ │ │ │ └── dimens.xml │ │ │ │ ├── values │ │ │ │ │ ├── dimens.xml │ │ │ │ │ ├── arrays.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── themes.xml │ │ │ │ │ └── strings.xml │ │ │ │ ├── xml │ │ │ │ │ └── network_security_config.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── drawable │ │ │ │ │ ├── ic_baseline_keyboard_arrow_up_24.xml │ │ │ │ │ ├── ic_baseline_keyboard_arrow_down_24.xml │ │ │ │ │ ├── ic_baseline_polymer_24.xml │ │ │ │ │ ├── ic_baseline_music_note_24.xml │ │ │ │ │ ├── ic_baseline_privacy_tip_24.xml │ │ │ │ │ ├── ic_baseline_verified_user_24.xml │ │ │ │ │ ├── bg_stroke_corner_grey.xml │ │ │ │ │ ├── ic_baseline_double_arrow_24.xml │ │ │ │ │ ├── ic_baseline_dynamic_form_24_notify_merge.xml │ │ │ │ │ ├── ic_baseline_refresh_24.xml │ │ │ │ │ ├── ic_baseline_ac_unit_24.xml │ │ │ │ │ ├── ic_baseline_follow_the_signs_24.xml │ │ │ │ │ ├── ic_baseline_policy_24.xml │ │ │ │ │ ├── ic_baseline_dynamic_feed_24_notify_filter.xml │ │ │ │ │ ├── ic_baseline_nat_24.xml │ │ │ │ │ ├── ic_baseline_qr_code_scanner_24.xml │ │ │ │ │ ├── ic_baseline_alt_route_24.xml │ │ │ │ │ ├── ic_baseline_grass_24.xml │ │ │ │ │ ├── github.xml │ │ │ │ │ ├── ic_baseline_sports_kabaddi_24.xml │ │ │ │ │ ├── magisk.xml │ │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── values-v31 │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── themes.xml │ │ │ │ ├── menu │ │ │ │ │ ├── menu_about.xml │ │ │ │ │ └── menu_main.xml │ │ │ │ ├── values-night │ │ │ │ │ ├── themes.xml │ │ │ │ │ └── colors.xml │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ └── layout │ │ │ │ │ ├── still_not_done.xml │ │ │ │ │ ├── content_scrolling.xml │ │ │ │ │ ├── activity_about.xml │ │ │ │ │ └── activity_main.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── coolest │ │ │ │ │ └── toolbox │ │ │ │ │ ├── utils │ │ │ │ │ ├── MainThreadHelper.kt │ │ │ │ │ ├── TimeUtil.kt │ │ │ │ │ ├── ShowHideAnimUtil.java │ │ │ │ │ ├── BingDailyWallpaper.kt │ │ │ │ │ ├── CoolConfigHelper.kt │ │ │ │ │ ├── ReleaseNotesUtil.kt │ │ │ │ │ ├── CoolConfigHelper_28.kt │ │ │ │ │ ├── GithubUpdateUtils.kt │ │ │ │ │ └── ViewUtils.java │ │ │ │ │ ├── MyApplication.kt │ │ │ │ │ ├── xposed │ │ │ │ │ ├── ContextHelper.kt │ │ │ │ │ └── HookEntry.kt │ │ │ │ │ └── ui │ │ │ │ │ ├── KotlinUtils.kt │ │ │ │ │ ├── CoolUtils.java │ │ │ │ │ └── AboutActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── coolest │ │ │ │ └── toolbox │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── coolest │ │ │ └── toolbox │ │ │ └── ExampleInstrumentedTest.kt │ ├── release │ │ ├── app-release.apk │ │ └── output-metadata.json │ ├── proguard-rules.pro │ └── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle ├── build.gradle ├── gradle.properties ├── gradlew.bat └── gradlew └── README.md /SCOPE: -------------------------------------------------------------------------------- 1 | [".android.settings","com.xiaomi.mirror"] 2 | -------------------------------------------------------------------------------- /SUMMARY: -------------------------------------------------------------------------------- 1 | 解锁小米平板MIUI+连电脑, 同时让不支持MIUI+的手机支持MIUI+ 2 | -------------------------------------------------------------------------------- /SOURCE_URL: -------------------------------------------------------------------------------- 1 | https://github.com/CoolestEnoch/MIUI_Tools 2 | -------------------------------------------------------------------------------- /code/app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.coolest.toolbox.xposed.HookEntry -------------------------------------------------------------------------------- /code/app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/release/app-release.apk -------------------------------------------------------------------------------- /code/app/src/main/res/values-w1240dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 200dp 3 | -------------------------------------------------------------------------------- /code/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/off.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/snhj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/snhj.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/ysxb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/ysxb.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/caixing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/caixing.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxxhdpi/jinfan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxxhdpi/jinfan.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/ic_menu_send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/ic_menu_send.png -------------------------------------------------------------------------------- /code/app/src/main/res/values-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 48dp 3 | 48dp 4 | -------------------------------------------------------------------------------- /code/app/src/main/res/values-w600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 48dp 3 | 48dp 4 | -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/notepaad_win11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/notepaad_win11.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/sunglass_huaji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/sunglass_huaji.png -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-xxhdpi/ic_lockscreen_answer_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolestEnoch/MIUI_Tools/HEAD/code/app/src/main/res/mipmap-xxhdpi/ic_lockscreen_answer_active.png -------------------------------------------------------------------------------- /code/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 180dp 3 | 16dp 4 | 16dp 5 | -------------------------------------------------------------------------------- /code/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /code/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 16 11:39:32 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /code/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /code/settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | jcenter() // Warning: this repository is going to shut down soon 7 | } 8 | } 9 | rootProject.name = "MIUI工具箱" 10 | include ':app' 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MIUI工具箱 2 | 3 | 可解锁平板通过MIUI+连接电脑 4 | 5 | 可修改MIUI+内电脑名字和电脑小尾巴 6 | 7 | 还有一些其他功能 8 | 9 | 10 | 11 | **更新随缘** 12 | 13 | [LSPosed仓库链接](https://github.com/Xposed-Modules-Repo/com.coolest.toolbox) 14 | 15 | [最新版下载](https://github.com/CoolestEnoch/MIUI_Tools/releases/latest) 16 | 17 | [手机版MIUI+下载链接](https://github.com/CoolestEnoch/MIUI_Tools/releases/download/V1.6/MIUI%2B_phone_3.5.21.apk) 18 | (其实从手机上提取一个装平板上不就得了(受虐滑稽)) 19 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/MainThreadHelper.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | 6 | 7 | val mainHandler: Handler by lazy { 8 | Handler(Looper.getMainLooper()) 9 | } 10 | 11 | fun runOnMainThread(r: Runnable) { 12 | if (Looper.myLooper() == Looper.getMainLooper()) { 13 | r.run() 14 | } else { 15 | mainHandler.post(r) 16 | } 17 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/material_dynamic_primary40 4 | @color/material_dynamic_neutral70 5 | @color/material_dynamic_neutral_variant70 6 | @color/material_dynamic_tertiary70 7 | @color/material_dynamic_tertiary90 8 | -------------------------------------------------------------------------------- /code/app/src/test/java/com/coolest/toolbox/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.coolest.toolbox", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "3.01", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_polymer_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_music_note_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_privacy_tip_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.xiaomi.mirror 7 | com.android.settings 8 | com.miui.misound 9 | android 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_verified_user_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/bg_stroke_corner_grey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_double_arrow_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /code/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath "com.android.tools.build:gradle:7.0.1" 9 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | task clean(type: Delete) { 17 | delete rootProject.buildDir 18 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_dynamic_form_24_notify_merge.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import com.coolest.toolbox.utils.getTodayWallpaperURL 8 | import java.io.DataInputStream 9 | import java.net.URL 10 | import kotlin.concurrent.thread 11 | 12 | class MyApplication : Application() { 13 | 14 | companion object { 15 | var dailyBingPaper: Bitmap? = null 16 | var appContext: Context? = null 17 | } 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/xposed/ContextHelper.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.xposed 2 | 3 | import android.app.AndroidAppHelper 4 | import android.content.Context 5 | import com.github.kyuubiran.ezxhelper.utils.findMethod 6 | import com.github.kyuubiran.ezxhelper.utils.hookMethod 7 | import de.robv.android.xposed.XC_MethodHook 8 | import de.robv.android.xposed.XposedBridge 9 | import de.robv.android.xposed.XposedHelpers.findAndHookMethod 10 | import de.robv.android.xposed.XposedHelpers.findClass 11 | import de.robv.android.xposed.callbacks.XC_LoadPackage 12 | 13 | 14 | fun getContext() = AndroidAppHelper.currentApplication().applicationContext -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_ac_unit_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/TimeUtil.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import java.text.ParsePosition 4 | import java.text.SimpleDateFormat 5 | 6 | /** 7 | * Timestamp to String 8 | * @param Timestamp 9 | * @return String 10 | */ 11 | public fun transToString(time: Long): String { 12 | return SimpleDateFormat("YYYY年MM月DD日 hh时mm分ss秒").format(time) 13 | } 14 | 15 | /** 16 | * String to Timestamp 17 | * @param String 18 | * @return Timestamp 19 | */ 20 | 21 | public fun transToTimeStamp(date: String): Long { 22 | //2022-03-19T15:03:55Z 23 | return SimpleDateFormat("YYYY-MM-DD'T'hh:mm:ss'Z'").parse(date, ParsePosition(0)).time 24 | } 25 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_follow_the_signs_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/menu/menu_about.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 16 | 21 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_policy_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_dynamic_feed_24_notify_filter.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_nat_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_qr_code_scanner_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/androidTest/java/com/coolest/toolbox/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.coolest.toolbox", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /code/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_alt_route_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_grass_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /code/app/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /code/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF4EAFF5 9 | #ff444444 10 | #FF000000 11 | #FFFFFFFF 12 | 13 | #FFFF0000 14 | #FFFF7F00 15 | #FFFFFF00 16 | #FF00FF00 17 | #FF00FFFF 18 | #FF0000FF 19 | #FF8B00FF 20 | 21 | 22 | @color/teal_200 23 | 24 | 25 | @color/black_icon 26 | -------------------------------------------------------------------------------- /code/app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 17 | 18 | 19 | 24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/github.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /code/app/src/main/assets/easygo.json: -------------------------------------------------------------------------------- 1 | { 2 | "easyGoVersion": "1.0", 3 | "client": "com.coolest.toolbox", 4 | "logicEntities": [ 5 | { 6 | "head": { 7 | "function": "magicwindow", 8 | "required": "true" 9 | }, 10 | "body": { 11 | "mode": "1", 12 | "defaultDualActivities": { 13 | "mainPages": "net.micode.notes.ui.NotesListActivity", 14 | "relatedPage": "" 15 | }, 16 | "Activities": [ 17 | { 18 | "name": "net.micode.notes.ui.NoteEditActivity", 19 | "defaultFullScreen": "false" 20 | } 21 | ], 22 | "activityPairs": [ 23 | { 24 | "from": "net.micode.notes.ui.NotesListActivity", 25 | "to": "*" 26 | } 27 | ], 28 | "UX": { 29 | "windowsRatio": [ 30 | { 31 | "device": "PAD", 32 | "ratio": "900|1654" 33 | } 34 | ], 35 | "supportRotationUxCompat": "true", 36 | "isDraggable": "true" 37 | } 38 | } 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /code/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF4EAFF5 9 | #ff444444 10 | #FF000000 11 | #FFFFFFFF 12 | 13 | #FFFF0000 14 | #FFFF7F00 15 | #FFFFFF00 16 | #FF00FF00 17 | #FF00FFFF 18 | #FF0000FF 19 | #FF8B00FF 20 | 21 | 22 | @color/black 23 | 24 | @color/purple_500 25 | @color/light_blue 26 | @color/light_blue 27 | #00aaaaaa 28 | 29 | #e8e8e8 30 | -------------------------------------------------------------------------------- /code/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/ui/KotlinUtils.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.ui 2 | 3 | import android.Manifest 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.google.android.material.snackbar.Snackbar 6 | import com.permissionx.guolindev.PermissionX 7 | 8 | class KotlinUtils { 9 | 10 | companion object { 11 | 12 | fun request_permissions(activity: AppCompatActivity) { 13 | PermissionX.init(activity) 14 | .permissions( 15 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 16 | Manifest.permission.READ_EXTERNAL_STORAGE, 17 | Manifest.permission.MANAGE_EXTERNAL_STORAGE 18 | ) 19 | .explainReasonBeforeRequest() 20 | .onExplainRequestReason { scope, deniedList -> 21 | scope.showRequestReasonDialog(deniedList, "配置文件存取权限", "好", "取消") 22 | } 23 | .onForwardToSettings { scope, deniedList -> 24 | scope.showForwardToSettingsDialog( 25 | deniedList, 26 | "不授予权限则可能出现闪退!", 27 | "好", 28 | "取消" 29 | ) 30 | } 31 | .request { allGranted, grantedList, deniedList -> 32 | if (allGranted) { 33 | Snackbar.make( 34 | activity.window.decorView, 35 | "权限状态正常", 36 | Snackbar.LENGTH_LONG 37 | ).show() 38 | } else { 39 | Snackbar.make( 40 | activity.window.decorView, 41 | "以下权限被拒绝: $deniedList", 42 | Snackbar.LENGTH_LONG 43 | ).show() 44 | } 45 | } 46 | 47 | /*//判断能否在在安卓11以上的设备免root管理Android/data 48 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { 49 | //表明已经有这个权限了 50 | val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) 51 | startActivity(intent) 52 | }*/ 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_baseline_sports_kabaddi_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /code/app/src/main/res/layout/still_not_done.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 26 | 27 | 34 | 35 | 43 | 44 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /code/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 41 | 44 | 47 | 50 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/ShowHideAnimUtil.java: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | public class ShowHideAnimUtil { 8 | /** 9 | * 将view从可见变为不可见的动画,原理:动态改变其LayoutParams.height的值 10 | * @param view 要展示动画的view 11 | */ 12 | public static void hideAnimator_height(final View view){ 13 | if(view!=null){ 14 | int viewHeight=view.getHeight(); 15 | if(viewHeight==0){ 16 | int width=View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); 17 | int height=View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); 18 | view.measure(width,height); 19 | viewHeight=view.getMeasuredHeight(); 20 | } 21 | ValueAnimator animator=ValueAnimator.ofInt(viewHeight,0); 22 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 23 | @Override 24 | public void onAnimationUpdate(ValueAnimator animation) { 25 | ViewGroup.LayoutParams params=view.getLayoutParams(); 26 | params.height= (int) animation.getAnimatedValue(); 27 | view.setLayoutParams(params); 28 | } 29 | }); 30 | animator.start(); 31 | } 32 | } 33 | 34 | /** 35 | * 动态改变view的高度动画效果,动画时长300毫秒[android属性动画默认时长] 36 | * 原理:动画改变view LayoutParams.height的值 37 | * @param view 要进行高度改变动画的view 38 | * @param startHeight 动画前的view的高度 39 | * @param endHeight 动画后的view的高度 40 | */ 41 | public static void showAnimator_height(final View view, final int startHeight, final int endHeight){ 42 | if(view!=null&&startHeight>=0&&endHeight>=0){ 43 | ValueAnimator animator=ValueAnimator.ofInt(startHeight,endHeight); 44 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 45 | @Override 46 | public void onAnimationUpdate(ValueAnimator animation) { 47 | ViewGroup.LayoutParams params=view.getLayoutParams(); 48 | params.height= (int) animation.getAnimatedValue(); 49 | view.setLayoutParams(params); 50 | } 51 | }); 52 | animator.start(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /code/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.coolest.toolbox" 11 | minSdk 25 12 | targetSdk 31 13 | versionCode 1 14 | versionName "3.01" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildFeatures{ 20 | viewBinding true 21 | } 22 | 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | kotlinOptions { 34 | jvmTarget = '1.8' 35 | } 36 | } 37 | 38 | dependencies { 39 | 40 | // implementation 'androidx.core:core-ktx:1.3.2' 41 | implementation 'androidx.appcompat:appcompat:1.2.0' 42 | implementation 'com.google.android.material:material:1.3.0' 43 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 44 | testImplementation 'junit:junit:4.+' 45 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 47 | 48 | compileOnly 'de.robv.android.xposed:api:82' 49 | compileOnly 'de.robv.android.xposed:api:82:sources' 50 | implementation 'com.github.kyuubiran:EzXHelper:0.6.3' 51 | 52 | implementation 'com.guolindev.permissionx:permissionx:1.6.1' 53 | 54 | api 'com.google.android.material:material:1.5.0' 55 | implementation 'com.afollestad.material-dialogs:core:3.2.1' 56 | implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' 57 | 58 | implementation 'com.github.bumptech.glide:glide:4.9.0' 59 | implementation 'com.squareup.okhttp3:okhttp:4.8.1' 60 | 61 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' 62 | implementation 'jp.wasabeef:glide-transformations:4.0.1' 63 | } -------------------------------------------------------------------------------- /code/app/src/main/res/layout/content_scrolling.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 20 | 23 | 24 | 28 | 29 | 38 | 39 | 48 | 49 | 56 | 57 | 58 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /code/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/BingDailyWallpaper.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.content.Context 4 | import android.text.Spannable 5 | import android.text.SpannableStringBuilder 6 | import android.text.style.AbsoluteSizeSpan 7 | import android.util.Log 8 | import android.widget.ImageView 9 | import com.bumptech.glide.Glide 10 | import com.google.android.material.snackbar.Snackbar 11 | import de.robv.android.xposed.XposedBridge 12 | import jp.wasabeef.glide.transformations.BlurTransformation 13 | import okhttp3.OkHttpClient 14 | import okhttp3.Request 15 | import org.json.JSONObject 16 | import kotlin.concurrent.thread 17 | 18 | 19 | //implementation 'com.github.bumptech.glide:glide:4.9.0' 20 | //implementation 'com.squareup.okhttp3:okhttp:4.8.1' 21 | 22 | fun getTodayWallpaperURL(): String { 23 | //调用微软必应壁纸API获取json 24 | var imgUrl: String = "" 25 | var microsoftApiResponseJson: String? = "" 26 | val client = OkHttpClient() 27 | val request = Request.Builder() 28 | .url("https://cn.bing.com/HPImageArchive.aspx?n=1&format=js&idx=0") 29 | .build() 30 | val response = client.newCall(request).execute() 31 | microsoftApiResponseJson = response.body?.string() 32 | Log.e("okhttp", "$microsoftApiResponseJson") 33 | 34 | //解析json获取图片地址 35 | val jsonArray = JSONObject(microsoftApiResponseJson).getJSONArray("images") 36 | for (i in 0 until jsonArray.length()) { 37 | val jsonObject = jsonArray.get(i) as JSONObject 38 | if (jsonObject.has("url")) { 39 | imgUrl = jsonObject.getString("url") 40 | } 41 | } 42 | 43 | return "https://cn.bing.com/$imgUrl" 44 | } 45 | 46 | fun setBingDailyWallpaper(imageView: ImageView, context: Context, blurRadius: Int) { 47 | thread { 48 | try { 49 | val imgUrl = getTodayWallpaperURL() 50 | Log.e("okhttp", imgUrl) 51 | runOnMainThread { 52 | try { 53 | when { 54 | blurRadius < 1 -> Glide.with(context) 55 | .load(imgUrl) 56 | .into(imageView) 57 | else -> Glide.with(context).load(imgUrl) 58 | .transform(BlurTransformation(blurRadius)) 59 | .into(imageView) 60 | } 61 | // val text = "关于 壁纸来源: Bing每日壁纸" 62 | // val sb = SpannableStringBuilder(text) 63 | // sb.setSpan( 64 | // AbsoluteSizeSpan(ViewUtils_Kotlin.dp2px(context, 34).toInt()), 65 | // 0, 66 | // 4, 67 | // Spannable.SPAN_INCLUSIVE_INCLUSIVE 68 | // ) 69 | // sb.setSpan( 70 | // AbsoluteSizeSpan(ViewUtils_Kotlin.dp2px(context, 12).toInt()), 71 | // 4, 72 | // text.length, 73 | // Spannable.SPAN_INCLUSIVE_INCLUSIVE 74 | // ) 75 | 76 | // binding.toolbarLayout.title = sb 77 | } catch (e: java.lang.Exception) { 78 | e.printStackTrace() 79 | XposedBridge.log(e) 80 | } 81 | } 82 | } catch (e: Exception) { 83 | if (e.toString().contains("Unable to resolve host \"api.github.com\"")) { 84 | Snackbar.make( 85 | imageView.rootView, 86 | "无法连接到必应服务器, 请检查网络", 87 | Snackbar.LENGTH_LONG 88 | ).show() 89 | } else { 90 | e.printStackTrace() 91 | XposedBridge.log(e) 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /code/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 21 | 22 | 31 | 32 | 41 | 42 | 52 | 53 | 64 | 65 | 72 | 73 | 77 | 78 | 84 | 85 | 88 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/CoolConfigHelper.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ContentValues 5 | import android.content.Context 6 | import android.graphics.Bitmap 7 | import android.net.Uri 8 | import android.os.Build 9 | import android.os.Environment 10 | import android.provider.MediaStore 11 | import androidx.annotation.RequiresApi 12 | import com.coolest.toolbox.ui.CoolUtils 13 | import com.github.kyuubiran.ezxhelper.utils.Log 14 | import de.robv.android.xposed.XposedBridge 15 | import org.json.JSONArray 16 | import org.json.JSONObject 17 | import java.io.* 18 | 19 | class CoolConfigHelper { 20 | companion object { 21 | //const val configTxtFile = File(Context().externalCacheDir, "config.txt") 22 | private var configFileStr = 23 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) 24 | .toString() + "/config.txt" 25 | 26 | // private var configFileStr = "/storage/emulated/0/Download/config.txt" 27 | 28 | /* fun initJsonConfig(): Boolean { 29 | try { 30 | //保存为json 31 | val obj = JSONObject().apply { 32 | put("miui_plus_regard_as_phone", false) 33 | } 34 | 35 | //把json转字符串并保存到本地 36 | val content = obj.toString() 37 | val file = File(configTxtFile) 38 | 39 | // if file doesnt exists, then create it 40 | if (!file.exists()) { 41 | file.createNewFile() 42 | } 43 | val fw = FileWriter(file.absoluteFile) 44 | val bw = BufferedWriter(fw) 45 | bw.write(content) 46 | bw.close() 47 | } catch (e: Exception) { 48 | e.printStackTrace() 49 | 50 | return false 51 | } 52 | 53 | return true 54 | }*/ 55 | 56 | fun initJsonConfig(context: Context): Boolean { 57 | try { 58 | //保存为json 59 | val obj = JSONObject().apply { 60 | put("miui_plus_connect_pc", !CoolUtils.isPad()) 61 | put("miui_plus_cust_pc_name_switch", false) 62 | put("miui_plus_pc_name", "") 63 | put("miui_plus_pc_tail", "") 64 | put("dolby_enabled", CoolUtils.isPad()) 65 | put("harmanKardon_enabled", true) 66 | } 67 | Log.e(obj.toString()) 68 | 69 | val myFile = File(configFileStr) 70 | myFile.printWriter().use { out -> 71 | out.println(obj.toString()) 72 | } 73 | } catch (e: Exception) { 74 | e.printStackTrace() 75 | return false 76 | } 77 | return true 78 | } 79 | 80 | 81 | fun modifyJsonConfig(context: Context, key: String, value: Any): Boolean { 82 | try { 83 | //文件不存在就创建 84 | if (!File(configFileStr).exists()) { 85 | initJsonConfig(context) 86 | } 87 | 88 | 89 | //读取配置文件 90 | val jsonContent = StringBuilder() 91 | val input = FileInputStream(File(configFileStr)) 92 | val reader = BufferedReader(InputStreamReader(input)) 93 | reader.use { 94 | reader.forEachLine { 95 | jsonContent.append(it) 96 | } 97 | } 98 | 99 | //改配置 100 | val jsonString = jsonContent.toString() 101 | val obj = JSONObject(jsonString) 102 | obj.put(key, value)//修改json配置文件的配置项。put会覆盖旧值 103 | 104 | //保存文件 105 | val myFile = File(configFileStr) 106 | myFile.printWriter().use { out -> 107 | out.println(obj.toString()) 108 | } 109 | 110 | } catch (e: Exception) { 111 | e.printStackTrace() 112 | return false 113 | } 114 | 115 | return true 116 | } 117 | 118 | 119 | fun getJsonConfig(context: Context, key: String): Any? { 120 | //文件不存在就创建 121 | val jsonContent = StringBuilder() 122 | try { 123 | val input = FileInputStream(File(configFileStr)) 124 | val reader = BufferedReader(InputStreamReader(input)) 125 | reader.use { 126 | reader.forEachLine { 127 | jsonContent.append(it) 128 | } 129 | } 130 | 131 | val jsonString = jsonContent.toString() 132 | val json = JSONObject(jsonString)//拿到的配置文件json object 133 | return json.get(key) 134 | } catch (e: Exception) { 135 | e.printStackTrace() 136 | return null 137 | } 138 | } 139 | } 140 | } -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/magisk.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | -------------------------------------------------------------------------------- /code/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MIUI工具箱 3 | ScrollingActivity 4 | 5 | "Material is the metaphor.\n\n" 6 | 7 | "A material metaphor is the unifying theory of a rationalized space and a system of motion." 8 | "The material is grounded in tactile reality, inspired by the study of paper and ink, yet " 9 | "technologically advanced and open to imagination and magic.\n" 10 | "Surfaces and edges of the material provide visual cues that are grounded in reality. The " 11 | "use of familiar tactile attributes helps users quickly understand affordances. Yet the " 12 | "flexibility of the material creates new affordances that supercede those in the physical " 13 | "world, without breaking the rules of physics.\n" 14 | "The fundamentals of light, surface, and movement are key to conveying how objects move, " 15 | "interact, and exist in space and in relation to each other. Realistic lighting shows " 16 | "seams, divides space, and indicates moving parts.\n\n" 17 | 18 | "Bold, graphic, intentional.\n\n" 19 | 20 | "The foundational elements of print based design typography, grids, space, scale, color, " 21 | "and use of imagery guide visual treatments. These elements do far more than please the " 22 | "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge " 23 | "imagery, large scale typography, and intentional white space create a bold and graphic " 24 | "interface that immerse the user in the experience.\n" 25 | "An emphasis on user actions makes core functionality immediately apparent and provides " 26 | "waypoints for the user.\n\n" 27 | 28 | "Motion provides meaning.\n\n" 29 | 30 | "Motion respects and reinforces the user as the prime mover. Primary user actions are " 31 | "inflection points that initiate motion, transforming the whole design.\n" 32 | "All action takes place in a single environment. Objects are presented to the user without " 33 | "breaking the continuity of experience even as they transform and reorganize.\n" 34 | "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. " 35 | "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n" 36 | 37 | "3D world.\n\n" 38 | 39 | "The material environment is a 3D space, which means all objects have x, y, and z " 40 | "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the " 41 | "positive z-axis extending towards the viewer. Every sheet of material occupies a single " 42 | "position along the z-axis and has a standard 1dp thickness.\n" 43 | "On the web, the z-axis is used for layering and not for perspective. The 3D world is " 44 | "emulated by manipulating the y-axis.\n\n" 45 | 46 | "Light and shadow.\n\n" 47 | 48 | "Within the material environment, virtual lights illuminate the scene. Key lights create " 49 | "directional shadows, while ambient light creates soft shadows from all angles.\n" 50 | "Shadows in the material environment are cast by these two light sources. In Android " 51 | "development, shadows occur when light sources are blocked by sheets of material at " 52 | "various positions along the z-axis. On the web, shadows are depicted by manipulating the " 53 | "y-axis only. The following example shows the card with a height of 6dp.\n\n" 54 | 55 | "Resting elevation.\n\n" 56 | 57 | "All material objects, regardless of size, have a resting elevation, or default elevation " 58 | "that does not change. If an object changes elevation, it should return to its resting " 59 | "elevation as soon as possible.\n\n" 60 | 61 | "Component elevations.\n\n" 62 | 63 | "The resting elevation for a component type is consistent across apps (e.g., FAB elevation " 64 | "does not vary from 6dp in one app to 16dp in another app).\n" 65 | "Components may have different resting elevations across platforms, depending on the depth " 66 | "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n" 67 | 68 | "Responsive elevation and dynamic elevation offsets.\n\n" 69 | 70 | "Some component types have responsive elevation, meaning they change elevation in response " 71 | "to user input (e.g., normal, focused, and pressed) or system events. These elevation " 72 | "changes are consistently implemented using dynamic elevation offsets.\n" 73 | "Dynamic elevation offsets are the goal elevation that a component moves towards, relative " 74 | "to the component’s resting state. They ensure that elevation changes are consistent " 75 | "across actions and component types. For example, all components that lift on press have " 76 | "the same elevation change relative to their resting elevation.\n" 77 | "Once the input event is completed or cancelled, the component will return to its resting " 78 | "elevation.\n\n" 79 | 80 | "Avoiding elevation interference.\n\n" 81 | 82 | "Components with responsive elevations may encounter other components as they move between " 83 | "their resting elevations and dynamic elevation offsets. Because material cannot pass " 84 | "through other material, components avoid interfering with one another any number of ways, " 85 | "whether on a per component basis or using the entire app layout.\n" 86 | "On a component level, components can move or be removed before they cause interference. " 87 | "For example, a floating action button (FAB) can disappear or move off screen before a " 88 | "user picks up a card, or it can move if a snackbar appears.\n" 89 | "On the layout level, design your app layout to minimize opportunities for interference. " 90 | "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere " 91 | "when a user tries to pick up one of cards.\n\n" 92 | 93 | Settings 94 | AboutActivity 95 | -------------------------------------------------------------------------------- /code/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /code/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/ReleaseNotesUtil.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import android.widget.LinearLayout 6 | import com.coolest.toolbox.R 7 | import android.widget.ScrollView 8 | import android.view.ViewGroup 9 | import androidx.appcompat.app.AlertDialog 10 | import java.lang.StringBuilder 11 | import java.util.* 12 | 13 | object ReleaseNotesUtil { 14 | var noteList: MutableList = ArrayList() 15 | var alert: AlertDialog? = null 16 | var builder: AlertDialog.Builder? = null 17 | const val tab_and_new_line = "\n" 18 | fun showAlertDialog(title: String?, str: String?, context: Context?) { 19 | builder = AlertDialog.Builder(context!!) 20 | alert = builder!! 21 | .setTitle(title) 22 | .setMessage(str) 23 | .setPositiveButton("确定") { dialog, which -> }.create() //创建AlertDialog对象 24 | alert!!.show() //显示对话框 25 | /*alert = builder!! 26 | .setTitle(title) 27 | .setMessage(str) 28 | .setNegativeButton("取消") { dialog, which -> 29 | Toast.makeText( 30 | context, 31 | "你点击了取消按钮~", 32 | Toast.LENGTH_SHORT 33 | ).show() 34 | } 35 | .setPositiveButton("确定", object : DialogInterface.OnClickListener { 36 | fun onClck(dialog: DialogInterface?, which: Int) { 37 | Toast.makeText(context, "你点击了确定按钮~", Toast.LENGTH_SHORT).show() 38 | } 39 | }) 40 | .setNeutralButton("中立") { dialog, which -> 41 | Toast.makeText( 42 | context, 43 | "你点击了中立按钮~", 44 | Toast.LENGTH_SHORT 45 | ).show() 46 | } 47 | .create() //创建AlertDialog对象 48 | alert!!.show() //显示对话框*/ 49 | } 50 | 51 | fun setReleaseNotes() { 52 | noteList.apply { 53 | add(Version(1.0, "第一个版本")) 54 | add( 55 | Version( 56 | 1.1, "[优化]在已root设备上的启动速度" 57 | + tab_and_new_line + "[新增]root授权防误触" 58 | + tab_and_new_line + "[新增]能看更新日志啦~" 59 | ) 60 | ) 61 | add(Version(1.2, "[新增]适配MIUI设备小白条沉浸" + tab_and_new_line + "[新增]适配MIUI平行世界")) 62 | add( 63 | Version( 64 | 1.3, 65 | "[新增]添加XPosed模块支持" + tab_and_new_line + "[新增]支持解锁米板5 MIUI+连电脑" + tab_and_new_line + "[新增]让运行MIUI12.5及以上系统的不支持MIUI+的手机支持MIUI+" 66 | ) 67 | ) 68 | add(Version(1.4, "[新增]连接MIUI+之后可修改本机显示的电脑名")) 69 | add( 70 | Version( 71 | 1.5, 72 | "[优化]大幅重构了UI" + tab_and_new_line + "[新增]支持修改MIUI+设置里电脑的小尾巴" + tab_and_new_line + "[新增]关于作者页及其每日壁纸背景图(需联网)" + tab_and_new_line + "[新增]支持在线检查更新和查询所有历史版本" 73 | ) 74 | ) 75 | add(Version(1.6, "[修复]操作过快导致的几率性闪退问题" + tab_and_new_line + "[修复]安卓12上检查更新可能引发的闪退")) 76 | add( 77 | Version( 78 | 1.7, 79 | "[修复]非MIUI设备上的显示问题" + tab_and_new_line + "[新增]更新渠道切换" + tab_and_new_line + "[新增]部分按钮高斯模糊" + tab_and_new_line + "[修复]部分场景下的闪退问题" + tab_and_new_line + "[修复]时间显示错误" 80 | ) 81 | ) 82 | add(Version(1.71, "[优化]部分场景的流畅度")) 83 | add(Version(1.72, "[优化]重构部分底层代码")) 84 | add(Version(2.0, "[适配]平板上版本号为3.5.17c的MIUI+")) 85 | add(Version(2.5, "[新增]解锁系统小窗应用个数从2个改成20个")) 86 | add(Version(2.51, "[修复]在非MIUI for Pad系统上会莫名其妙出现一堆FC的bug")) 87 | add( 88 | Version( 89 | 2.6, 90 | "[新增]解锁哈曼卡顿(仅在米10U和米板5的第一个MIUI13稳定版上测试过可用)" + tab_and_new_line + "[修复]深色模式显示异常" 91 | ) 92 | ) 93 | add(Version(2.61, "[改动]哈曼卡顿默认自动开启" + tab_and_new_line + "[优化]日志系统优化")) 94 | add( 95 | Version( 96 | 3.0, 97 | "[新增]MIUI13手机也能开好几个小窗啦" + tab_and_new_line + "[新增]任何程序皆可小窗" + tab_and_new_line + "[新增]锁定手机管家分数100分" + tab_and_new_line + "[新增]适配Material You动态配色(需Android 12或更高版本)" 98 | ) 99 | ) 100 | add(Version(3.01, "[修复]JoyUI无法使用的问题")) 101 | } 102 | Collections.reverse(noteList) 103 | } 104 | 105 | val releaseNotes: String 106 | get() { 107 | if (noteList.isEmpty()) setReleaseNotes() 108 | val sb = StringBuilder() 109 | for (v in noteList) { 110 | sb.append(v) 111 | } 112 | return sb.toString() 113 | } 114 | 115 | fun showReleaseNotes(context: Context) { 116 | // showAlertDialog("更新日志" + " " + "\n当前版本" + CoolUtils.getAppVersionName(context), getReleaseNotes(), context); 117 | releaseNotes 118 | val linearLayout = LinearLayout(context) 119 | val params = LinearLayout.LayoutParams( 120 | LinearLayout.LayoutParams.MATCH_PARENT, 121 | LinearLayout.LayoutParams.WRAP_CONTENT 122 | ) 123 | linearLayout.layoutParams = params 124 | linearLayout.orientation = LinearLayout.VERTICAL 125 | Log.e("for", "before") 126 | for (version in noteList) { 127 | val list = LinkedList() 128 | list.add(version!!.version.toString()) 129 | val detail = version.notes.split(tab_and_new_line.toRegex()).toTypedArray() 130 | for (str in detail) { 131 | list.add(str) 132 | } 133 | linearLayout.addView( 134 | ViewUtils_Kotlin.createBigButton( 135 | context, 136 | R.drawable.ic_baseline_grass_24, 137 | R.color.item_card_bg, 138 | list, null, R.color.white 139 | ) {} 140 | ) 141 | Log.e("for", "run in") 142 | } 143 | Log.e("for", "after") 144 | val scrollView = ScrollView(context) 145 | val scrollParams: ViewGroup.LayoutParams = LinearLayout.LayoutParams( 146 | LinearLayout.LayoutParams.MATCH_PARENT, 147 | LinearLayout.LayoutParams.WRAP_CONTENT 148 | ) 149 | scrollView.layoutParams = scrollParams 150 | scrollView.addView(linearLayout) 151 | ViewUtils.getBigCardFromBottom( 152 | context, 153 | "更新日志\t当前版本${ 154 | context.packageManager.getPackageInfo( 155 | context.packageName, 156 | 0 157 | ).versionName 158 | }", 159 | scrollView 160 | ).show() 161 | } 162 | 163 | fun showLastReleaseNotes(context: Context?) { 164 | // showAlertDialog("更新日志" + " " + "\n当前版本" + CoolUtils.getAppVersionName(context), getReleaseNotes(), context); 165 | releaseNotes 166 | val linearLayout = LinearLayout(context) 167 | val params = LinearLayout.LayoutParams( 168 | LinearLayout.LayoutParams.MATCH_PARENT, 169 | LinearLayout.LayoutParams.WRAP_CONTENT 170 | ) 171 | linearLayout.layoutParams = params 172 | linearLayout.orientation = LinearLayout.VERTICAL 173 | val version = noteList[0] 174 | val list = LinkedList() 175 | list.add(version!!.version.toString()) 176 | val detail = version.notes.split(tab_and_new_line.toRegex()).toTypedArray() 177 | for (str in detail) { 178 | list.add(str) 179 | } 180 | linearLayout.addView( 181 | ViewUtils.createCardView( 182 | context, 183 | R.color.item_card_bg, 184 | R.drawable.ic_baseline_grass_24, 185 | list 186 | ) 187 | ) 188 | Log.e("for", "run in") 189 | val scrollView = ScrollView(context) 190 | val scrollParams: ViewGroup.LayoutParams = LinearLayout.LayoutParams( 191 | LinearLayout.LayoutParams.MATCH_PARENT, 192 | LinearLayout.LayoutParams.WRAP_CONTENT 193 | ) 194 | scrollView.layoutParams = scrollParams 195 | scrollView.addView(linearLayout) 196 | ViewUtils.getBigCardFromBottom(context, "这个版本更新了啥", scrollView).show() 197 | } 198 | 199 | class Version(var version: Double, var notes: String) { 200 | override fun toString(): String { 201 | return "$version $notes\n" 202 | } 203 | } 204 | } -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/ui/CoolUtils.java: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.ui; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.os.Environment; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.DataOutputStream; 13 | import java.io.File; 14 | import java.io.FileInputStream; 15 | import java.io.IOException; 16 | import java.io.InputStreamReader; 17 | import java.util.Properties; 18 | 19 | import de.robv.android.xposed.XposedBridge; 20 | 21 | public class CoolUtils { 22 | 23 | public final String config_path = "/sdcard"; 24 | 25 | /** 26 | * 判断是否为MIUI系统 27 | * 28 | * @return 29 | */ 30 | public static boolean isMIUI() { 31 | String manufacturer = Build.MANUFACTURER.toLowerCase(); 32 | 33 | return !TextUtils.isEmpty(manufacturer) && (manufacturer.equals("xiaomi") || manufacturer.equals("blackshark")); 34 | } 35 | 36 | /** 37 | * 判断是否为平板 38 | * 39 | * @return 40 | */ 41 | public static boolean isPad() { 42 | return adbExec_without_root_stable("getprop ro.build.characteristics").contains("tablet"); 43 | } 44 | 45 | /** 46 | * 获取当前apk版本号 47 | * 48 | * @param context 49 | * @return 50 | */ 51 | public static String getAppVersionName(Context context) { 52 | String versionName = ""; 53 | try { 54 | // ---get the package info--- 55 | PackageManager pm = context.getPackageManager(); 56 | PackageInfo pi = pm.getPackageInfo(context.getPackageName(), 0); 57 | versionName = pi.versionName; 58 | // versioncode = pi.versionCode; 59 | if (versionName == null || versionName.length() <= 0) { 60 | return ""; 61 | } 62 | } catch (Exception e) { 63 | Log.e("VersionInfo", "Exception", e); 64 | XposedBridge.log(e); 65 | } 66 | return versionName; 67 | } 68 | 69 | /** 70 | * 判断手机是否已root 71 | * 72 | * @return 73 | */ 74 | public static boolean get_root_state() { 75 | boolean res = false; 76 | try { 77 | if ((!new File("/system/bin/su").exists()) && 78 | (!new File("/system/xbin/su").exists())) { 79 | res = false; 80 | } else { 81 | res = true; 82 | } 83 | ; 84 | } catch (Exception e) { 85 | 86 | } 87 | return res; 88 | } 89 | 90 | /** 91 | * root执行adb 稳定版 92 | * 93 | * @param cmd 94 | * @return 95 | */ 96 | public static String adbExec_with_root_stable(String cmd) { 97 | String content = ""; 98 | Process process = null; 99 | DataOutputStream os = null; 100 | try { 101 | process = Runtime.getRuntime().exec("su"); //切换到root帐号 102 | os = new DataOutputStream(process.getOutputStream()); 103 | os.writeBytes(cmd + "\n"); 104 | os.writeBytes("exit\n"); 105 | os.flush(); 106 | process.waitFor(); 107 | } catch (Exception e) { 108 | e.printStackTrace(); 109 | XposedBridge.log(e); 110 | // return "Failed, permission denied."; 111 | return content; 112 | } finally { 113 | try { 114 | if (os != null) { 115 | os.close(); 116 | } 117 | process.destroy(); 118 | } catch (Exception e) { 119 | e.printStackTrace(); 120 | XposedBridge.log(e); 121 | } 122 | } 123 | // return "Success."; 124 | return content; 125 | } 126 | 127 | /** 128 | * 无root执行adb 稳定版 129 | * 130 | * @param cmd 131 | * @return 132 | */ 133 | public static String adbExec_without_root_stable(String cmd) { 134 | String content = ""; 135 | Process process = null; 136 | BufferedReader reader = null; 137 | DataOutputStream os = null; 138 | try { 139 | process = Runtime.getRuntime().exec(cmd); //切换到root帐号 140 | os = new DataOutputStream(process.getOutputStream()); 141 | reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 142 | // os.writeBytes(cmd + "\n"); 143 | // os.writeBytes("exit\n"); 144 | os.flush(); 145 | process.waitFor(); 146 | StringBuffer output = new StringBuffer(); 147 | int read; 148 | char[] buffer = new char[4096]; 149 | while ((read = reader.read(buffer)) > 0) { 150 | output.append(buffer, 0, read); 151 | } 152 | reader.close(); 153 | content = output.toString(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | XposedBridge.log(e); 157 | // return "Failed, permission denied."; 158 | return content; 159 | } finally { 160 | try { 161 | if (os != null) { 162 | os.close(); 163 | } 164 | process.destroy(); 165 | } catch (Exception e) { 166 | e.printStackTrace(); 167 | XposedBridge.log(e); 168 | } 169 | } 170 | // return "Success."; 171 | return content; 172 | } 173 | 174 | //TODO 提权函数 175 | public static boolean get_root_access(String pkgCodePath) { 176 | Process process = null; 177 | DataOutputStream os = null; 178 | try { 179 | String cmd = "chmod 777 " + pkgCodePath; 180 | process = Runtime.getRuntime().exec("su"); //切换到root帐号 181 | os = new DataOutputStream(process.getOutputStream()); 182 | os.writeBytes(cmd + "\n"); 183 | os.writeBytes("exit\n"); 184 | os.flush(); 185 | process.waitFor(); 186 | 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | XposedBridge.log(e); 190 | return false; 191 | } finally { 192 | try { 193 | if (os != null) { 194 | os.close(); 195 | } 196 | process.destroy(); 197 | } catch (Exception e) { 198 | e.printStackTrace(); 199 | XposedBridge.log(e); 200 | } 201 | } 202 | return true; 203 | } 204 | 205 | public static String adbExec_with_root(String cmd) { 206 | String content = ""; 207 | Process process = null; 208 | BufferedReader reader = null; 209 | DataOutputStream os = null; 210 | try { 211 | process = Runtime.getRuntime().exec("su"); //切换到root帐号 212 | os = new DataOutputStream(process.getOutputStream()); 213 | reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 214 | os.writeBytes(cmd + "\n"); 215 | os.writeBytes("exit\n"); 216 | os.flush(); 217 | process.waitFor(); 218 | StringBuffer output = new StringBuffer(); 219 | int read; 220 | char[] buffer = new char[4096]; 221 | while ((read = reader.read(buffer)) > 0) { 222 | output.append(buffer, 0, read); 223 | } 224 | reader.close(); 225 | content = output.toString(); 226 | } catch (Exception e) { 227 | e.printStackTrace(); 228 | XposedBridge.log(e); 229 | // return "Failed, permission denied."; 230 | return content; 231 | } finally { 232 | try { 233 | if (os != null) { 234 | os.close(); 235 | } 236 | process.destroy(); 237 | } catch (Exception e) { 238 | e.printStackTrace(); 239 | XposedBridge.log(e); 240 | } 241 | } 242 | // return "Success."; 243 | return content; 244 | } 245 | 246 | /** 247 | * 执行adb命令_旧 248 | * 249 | * @param cmd 250 | * @return 251 | */ 252 | public static String adbExec_no_root(String cmd) { 253 | BufferedReader reader = null; 254 | String content = ""; 255 | try { 256 | //("ps -P|grep bg")执行失败,PC端adb shell ps -P|grep bg执行成功 257 | //Process process = Runtime.getRuntime().exec("ps -P|grep tv"); 258 | //-P 显示程序调度状态,通常是bg或fg,获取失败返回un和er 259 | // Process process = Runtime.getRuntime().exec("ps -P"); 260 | //打印进程信息,不过滤任何条件 261 | Process process = Runtime.getRuntime().exec(cmd); 262 | reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 263 | StringBuffer output = new StringBuffer(); 264 | int read; 265 | char[] buffer = new char[4096]; 266 | while ((read = reader.read(buffer)) > 0) { 267 | output.append(buffer, 0, read); 268 | } 269 | reader.close(); 270 | content = output.toString(); 271 | process.destroy(); 272 | } catch (Exception e) { 273 | e.printStackTrace(); 274 | XposedBridge.log(e); 275 | } 276 | return content; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/CoolConfigHelper_28.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ContentValues 5 | import android.content.Context 6 | import android.graphics.Bitmap 7 | import android.net.Uri 8 | import android.os.Build 9 | import android.os.Environment 10 | import android.provider.MediaStore 11 | import androidx.annotation.RequiresApi 12 | import com.coolest.toolbox.ui.CoolUtils 13 | import com.github.kyuubiran.ezxhelper.utils.Log 14 | import de.robv.android.xposed.XposedBridge 15 | import org.json.JSONArray 16 | import org.json.JSONObject 17 | import java.io.* 18 | 19 | class CoolConfigHelper_28 { 20 | companion object { 21 | //const val configTxtFile = File(Context().externalCacheDir, "config.txt") 22 | private var configFileStr = 23 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) 24 | .toString() + "/config.txt" 25 | 26 | // private var configFileStr = "/storage/emulated/0/Download/config.txt" 27 | private var configFileUri: Uri? = null 28 | 29 | /* fun initJsonConfig(): Boolean { 30 | try { 31 | //保存为json 32 | val obj = JSONObject().apply { 33 | put("miui_plus_regard_as_phone", false) 34 | } 35 | 36 | //把json转字符串并保存到本地 37 | val content = obj.toString() 38 | val file = File(configTxtFile) 39 | 40 | // if file doesnt exists, then create it 41 | if (!file.exists()) { 42 | file.createNewFile() 43 | } 44 | val fw = FileWriter(file.absoluteFile) 45 | val bw = BufferedWriter(fw) 46 | bw.write(content) 47 | bw.close() 48 | } catch (e: Exception) { 49 | e.printStackTrace() 50 | 51 | return false 52 | } 53 | 54 | return true 55 | }*/ 56 | 57 | @SuppressLint("InlinedApi") 58 | fun initJsonConfig(context: Context): Boolean { 59 | try { 60 | //保存为json 61 | val obj = JSONObject().apply { 62 | put("miui_plus_connect_pc", !CoolUtils.isPad()) 63 | put("miui_plus_cust_pc_name_switch", false) 64 | put("miui_plus_pc_name", "") 65 | put("miui_plus_pc_tail", "") 66 | put("dolby_enabled", CoolUtils.isPad()) 67 | put("harmanKardon_enabled", true) 68 | } 69 | Log.e(obj.toString()) 70 | 71 | when (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 72 | //安卓10以下 73 | false -> { 74 | val myFile = File(configFileStr) 75 | myFile.printWriter().use { out -> 76 | out.println(obj.toString()) 77 | } 78 | } 79 | //安卓10及以上 80 | true -> { 81 | val resolver = context.applicationContext.contentResolver 82 | // Find all audio files on the primary external storage device. 83 | val collection = 84 | MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) 85 | val details = ContentValues().apply { 86 | put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) 87 | put(MediaStore.Downloads.DISPLAY_NAME, "config.txt") 88 | put(MediaStore.Downloads.IS_PENDING, 1) 89 | } 90 | configFileUri = resolver.insert(collection, details) 91 | resolver.openFileDescriptor(configFileUri!!, "w", null).use { pfd -> 92 | // Write data into the pending audio file. 93 | val bos = 94 | BufferedOutputStream(FileOutputStream(pfd!!.fileDescriptor)) 95 | val pw = PrintWriter(bos) 96 | pw.println(obj.toString()) 97 | pw.flush() 98 | pw.close() 99 | bos.close() 100 | } 101 | details.clear() 102 | details.put(MediaStore.Downloads.IS_PENDING, 0) 103 | resolver.update(configFileUri!!, details, null, null) 104 | } 105 | } 106 | 107 | } catch (e: Exception) { 108 | e.printStackTrace() 109 | 110 | return false 111 | } 112 | 113 | return true 114 | } 115 | 116 | 117 | fun modifyJsonConfig(context: Context, key: String, value: Any): Boolean { 118 | try { 119 | //文件不存在就创建 120 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 121 | if (!File(configFileStr).exists()) { 122 | initJsonConfig(context) 123 | } 124 | } else { 125 | try { 126 | val resolver = context.applicationContext.contentResolver 127 | resolver.openInputStream(configFileUri!!).use { stream -> 128 | 129 | } 130 | } catch (e: java.lang.Exception) { 131 | // e.printStackTrace() 132 | initJsonConfig(context) 133 | } 134 | } 135 | 136 | 137 | //读取配置文件 138 | val jsonContent = StringBuilder() 139 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 140 | // Open a specific media item using InputStream. 141 | val resolver = context.applicationContext.contentResolver 142 | resolver.openInputStream(configFileUri!!).use { stream -> 143 | // Perform operations on "stream". 144 | val reader = BufferedReader(InputStreamReader(stream)) 145 | reader.use { 146 | reader.forEachLine { 147 | jsonContent.append(it) 148 | } 149 | } 150 | } 151 | } else { 152 | val input = FileInputStream(File(configFileStr)) 153 | val reader = BufferedReader(InputStreamReader(input)) 154 | reader.use { 155 | reader.forEachLine { 156 | jsonContent.append(it) 157 | } 158 | } 159 | } 160 | //改配置 161 | val jsonString = jsonContent.toString() 162 | val obj = JSONObject(jsonString) 163 | obj.put(key, value)//修改json配置文件的配置项。put会覆盖旧值 164 | 165 | //保存文件 166 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 167 | val myFile = File(configFileStr) 168 | myFile.printWriter().use { out -> 169 | out.println(obj.toString()) 170 | } 171 | } else { 172 | val resolver = context.applicationContext.contentResolver 173 | // Find all audio files on the primary external storage device. 174 | val collection = 175 | MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) 176 | val details = ContentValues().apply { 177 | put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) 178 | put(MediaStore.Downloads.DISPLAY_NAME, "config.txt") 179 | put(MediaStore.Downloads.IS_PENDING, 1) 180 | } 181 | configFileUri = resolver.insert(collection, details) 182 | resolver.openFileDescriptor(configFileUri!!, "w", null).use { pfd -> 183 | // Write data into the pending audio file. 184 | val bos = 185 | BufferedOutputStream(FileOutputStream(pfd!!.fileDescriptor)) 186 | val pw = PrintWriter(bos) 187 | pw.println(obj.toString()) 188 | pw.flush() 189 | pw.close() 190 | bos.close() 191 | } 192 | details.clear() 193 | details.put(MediaStore.Downloads.IS_PENDING, 0) 194 | resolver.update(configFileUri!!, details, null, null) 195 | } 196 | } catch (e: Exception) { 197 | e.printStackTrace() 198 | return false 199 | } 200 | 201 | return true 202 | } 203 | 204 | 205 | fun getJsonConfig(context: Context, key: String): Any? { 206 | //文件不存在就创建 207 | val jsonContent = StringBuilder() 208 | try { 209 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { 210 | val input = FileInputStream(File(configFileStr)) 211 | val reader = BufferedReader(InputStreamReader(input)) 212 | reader.use { 213 | reader.forEachLine { 214 | jsonContent.append(it) 215 | } 216 | } 217 | } else { 218 | // Open a specific media item using InputStream. 219 | val resolver = context.applicationContext.contentResolver 220 | resolver.openInputStream(configFileUri!!).use { stream -> 221 | // Perform operations on "stream". 222 | val reader = BufferedReader(InputStreamReader(stream)) 223 | reader.use { 224 | reader.forEachLine { 225 | jsonContent.append(it) 226 | } 227 | } 228 | } 229 | } 230 | val jsonString = jsonContent.toString() 231 | val json = JSONObject(jsonString)//拿到的配置文件json object 232 | return json.get(key) 233 | } catch (e: Exception) { 234 | e.printStackTrace() 235 | return null 236 | } 237 | } 238 | } 239 | } -------------------------------------------------------------------------------- /code/app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 22 | 23 | 32 | 33 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 63 | 64 | 68 | 69 | 77 | 78 | 83 | 84 | 88 | 89 | 93 | 94 | 103 | 104 | 113 | 114 | 121 | 122 | 123 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 147 | 148 | 156 | 157 | 162 | 163 | 167 | 168 | 172 | 173 | 182 | 183 | 192 | 193 | 201 | 202 | 203 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/ui/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.ui 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.text.TextUtils 8 | import android.util.Log 9 | import android.view.Menu 10 | import android.view.MenuItem 11 | import android.view.ViewGroup 12 | import android.widget.LinearLayout 13 | import android.widget.TextView 14 | import android.widget.Toast 15 | import androidx.appcompat.app.AppCompatActivity 16 | import com.afollestad.materialdialogs.MaterialDialog 17 | import com.coolest.toolbox.MyApplication 18 | import com.coolest.toolbox.R 19 | import com.coolest.toolbox.databinding.ActivityAboutBinding 20 | import com.coolest.toolbox.utils.* 21 | import com.google.android.material.card.MaterialCardView 22 | import com.google.android.material.dialog.MaterialDialogs 23 | import com.google.android.material.snackbar.Snackbar 24 | import de.robv.android.xposed.XposedBridge 25 | import okhttp3.OkHttpClient 26 | import okhttp3.Request 27 | import org.json.JSONObject 28 | import java.util.* 29 | import java.util.regex.Matcher 30 | import java.util.regex.Pattern 31 | import kotlin.concurrent.thread 32 | 33 | class AboutActivity : AppCompatActivity() { 34 | 35 | private lateinit var binding: ActivityAboutBinding 36 | 37 | var todayBingPicUrl = "" 38 | 39 | private var githubName = "CoolestEnoch" 40 | private var githubRepo = "MIUI_Tools" 41 | 42 | override fun onCreate(savedInstanceState: Bundle?) { 43 | super.onCreate(savedInstanceState) 44 | binding = ActivityAboutBinding.inflate(layoutInflater) 45 | setContentView(binding.root) 46 | 47 | setSupportActionBar(findViewById(R.id.toolbar)) 48 | 49 | binding.toolbarLayout.title = "关于" 50 | 51 | binding.cardAuthor.setOnClickListener { 52 | Snackbar.make(window.decorView, "作者: Coolest Enoch", Snackbar.LENGTH_LONG) 53 | .setAction("Action", null).show() 54 | try { 55 | Thread.sleep(100) 56 | } catch (e: InterruptedException) { 57 | e.printStackTrace() 58 | } 59 | Toast.makeText(this, "作者: Coolest Enoch", Toast.LENGTH_SHORT).show() 60 | startActivity(Intent().apply { 61 | action = "android.intent.action.VIEW" 62 | data = Uri.parse("https://github.com/coolestenoch") 63 | }) 64 | } 65 | 66 | //背景获取 67 | thread { 68 | try { 69 | todayBingPicUrl = getTodayWallpaperURL() 70 | } catch (e: Exception) { 71 | e.printStackTrace() 72 | XposedBridge.log(e) 73 | } 74 | } 75 | 76 | //背景图 77 | when (MyApplication.dailyBingPaper) { 78 | null -> setBingDailyWallpaper(binding.aboutBgImg, this, 0) 79 | else -> binding.aboutBgImg.setImageBitmap(MyApplication.dailyBingPaper) 80 | } 81 | 82 | 83 | //mapOf(信息list1 to 职位1, 信息list2 to 职位2) 84 | //如果map里的key list里只有一个值, 那么就会被解析为github用户名并直接去github上查找这个人的信息 85 | //否则list格式为:{用户名:String, 个性签名:String, 头像URL, 网页URL} 86 | //空值不要用null, 请使用空字符串""代替 87 | 88 | //作者列表 89 | val list = mapOf(listOf("coolestenoch") to "作者") 90 | processDeveloperInfo(list, binding.authorListView, null, this) 91 | 92 | //贡献者列表 93 | val list2 = mapOf(listOf("mikotwa") to "", listOf("桜谷暮枫", "", "http://avatar.coolapk.com/data/002/37/46/44_avatar_middle.jpg", "http://www.coolapk.com/u/2374644") to "为修复JoyUI上的bug提供帮助") 94 | processDeveloperInfo(list2, binding.contributorListView, binding.contributorListTip, this) 95 | 96 | //下拉刷新检查更新 97 | binding.swipeRefresh.apply { 98 | setColorSchemeColors( 99 | resources.getColor(R.color.green), 100 | resources.getColor(R.color.blue), 101 | resources.getColor(R.color.yellow), 102 | resources.getColor(R.color.purple), 103 | resources.getColor(R.color.orange), 104 | resources.getColor(R.color.red), 105 | resources.getColor(R.color.cyan) 106 | ) 107 | setOnRefreshListener { 108 | checkUpdate( 109 | packageManager.getPackageInfo(packageName, 0).versionName, 110 | binding.swipeRefresh, 111 | githubName, 112 | githubRepo 113 | ) 114 | } 115 | } 116 | } 117 | 118 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 119 | menuInflater.inflate(R.menu.menu_about, menu) 120 | return super.onCreateOptionsMenu(menu) 121 | } 122 | 123 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 124 | when (item.itemId) { 125 | R.id.action_update -> { 126 | //更新渠道设置 127 | getSharedPreferences("update_channel", Context.MODE_PRIVATE).apply { 128 | githubName = "${getString("username", "CoolestEnoch")}" 129 | githubRepo = "${getString("repo", "MIUI_Tools")}" 130 | } 131 | checkUpdate( 132 | packageManager.getPackageInfo(packageName, 0).versionName, 133 | binding.swipeRefresh, 134 | githubName, 135 | githubRepo 136 | ) 137 | } 138 | R.id.release_history -> { 139 | //更新渠道设置 140 | getSharedPreferences("update_channel", Context.MODE_PRIVATE).apply { 141 | githubName = "${getString("username", "CoolestEnoch")}" 142 | githubRepo = "${getString("repo", "MIUI_Tools")}" 143 | } 144 | showAllRelease( 145 | binding.swipeRefresh, when (githubName) { 146 | "CoolestEnoch" -> "所有版本: 官方渠道" 147 | "Xposed-Modules-Repo" -> "所有版本: LSPosed仓库" 148 | else -> "所有版本: 其他渠道" 149 | }, githubName, githubRepo 150 | ) 151 | } 152 | R.id.update_channel -> { 153 | changeUpdateChannel(this) 154 | } 155 | } 156 | return super.onOptionsItemSelected(item) 157 | } 158 | 159 | fun isHttpUrl(urls: String): Boolean { 160 | var isurl = false 161 | val regex = ("(((https|http)?://)?([a-z0-9]+[.])|(www.))" 162 | + "\\w+[.|\\/]([a-z0-9]{0,})?[[.]([a-z0-9]{0,})]+((/[\\S&&[^,;\u4E00-\u9FA5]]+)+)?([.][a-z0-9]{0,}+|/?)") //设置正则表达式 163 | val pat = Pattern.compile(regex.trim { it <= ' ' }) //对比 164 | val mat = pat.matcher(urls.trim { it <= ' ' }) 165 | isurl = mat.matches() //判断是否匹配 166 | if (isurl) { 167 | isurl = true 168 | } 169 | return isurl 170 | } 171 | 172 | fun processDeveloperInfo( 173 | userInfoMap: Map, String>?, 174 | developersView: ViewGroup, 175 | tipView: TextView?, 176 | context: Context 177 | ) { 178 | try { 179 | thread { 180 | val tempViewList = LinkedList() 181 | if (userInfoMap != null) { 182 | for ((userInfo, description) in userInfoMap) { 183 | if (userInfo.size == 1) { 184 | //是个github用户名 185 | val username = userInfo[0] 186 | 187 | try { 188 | //调用GitHub API获取json 189 | var githubResponseJson: String? = "" 190 | var avatar_url: String? = "" 191 | var nickName: String? = "" 192 | var bio: String? = "" 193 | val client = OkHttpClient() 194 | val request = Request.Builder() 195 | .url("https://api.github.com/users/$username") 196 | .build() 197 | val response = client.newCall(request).execute() 198 | githubResponseJson = response.body?.string() 199 | Log.e("github", "$githubResponseJson") 200 | 201 | //解析json获取图片地址 202 | val responseJson = JSONObject(githubResponseJson) 203 | avatar_url = "${responseJson.get("avatar_url")}" 204 | nickName = "${responseJson.get("login")}" 205 | bio = "${responseJson.get("bio")}" 206 | 207 | //添加卡片 208 | val textList = mutableListOf("") 209 | textList.clear() 210 | if(!username.equals("")) 211 | textList.add(nickName) 212 | if(!bio.equals("")) 213 | textList.add(bio) 214 | if(!description.equals("")) 215 | textList.add(description) 216 | 217 | runOnUiThread { 218 | tempViewList.add( 219 | ViewUtils_Kotlin.createBigButton( 220 | context, 221 | avatar_url, 222 | R.color.item_card_bg, 223 | textList, 224 | null, R.color.white 225 | ) { 226 | startActivity(Intent().apply { 227 | action = "android.intent.action.VIEW" 228 | data = Uri.parse("https://github.com/$username") 229 | }) 230 | } 231 | ) 232 | } 233 | runOnUiThread { 234 | developersView.removeAllViews() 235 | for (view in tempViewList) { 236 | developersView.addView(view) 237 | } 238 | } 239 | } catch (e: Exception) { 240 | if (e.toString() 241 | .contains("Unable to resolve host \"api.github.com\"") 242 | ) { 243 | Snackbar.make( 244 | window.decorView, 245 | "无法连接到GitHub服务器, 请检查网络", 246 | Snackbar.LENGTH_LONG 247 | ).show() 248 | } else { 249 | e.printStackTrace() 250 | XposedBridge.log(e) 251 | } 252 | } 253 | } else { 254 | //不是github用户名 255 | val username = userInfo[0] 256 | val bio = userInfo[1] 257 | val aviatorUrl = userInfo[2] 258 | val webPageUrl = userInfo[3] 259 | //还有一个description 260 | 261 | try { 262 | //添加卡片 263 | val textList = mutableListOf("") 264 | textList.clear() 265 | if(!username.equals("")) 266 | textList.add(username) 267 | if(!bio.equals("")) 268 | textList.add(bio) 269 | if(!description.equals("")) 270 | textList.add(description) 271 | 272 | runOnUiThread { 273 | tempViewList.add( 274 | ViewUtils_Kotlin.createBigButton( 275 | context, 276 | aviatorUrl, 277 | R.color.item_card_bg, 278 | textList, 279 | null, R.color.white 280 | ) { 281 | when (TextUtils.isEmpty(webPageUrl)) { 282 | false -> startActivity(Intent().apply { 283 | action = "android.intent.action.VIEW" 284 | data = Uri.parse(webPageUrl) 285 | }) 286 | true -> { 287 | Snackbar.make( 288 | window.decorView, 289 | "这个人很神秘, 没有留下可访问的页面。", 290 | Snackbar.LENGTH_LONG 291 | ).show() 292 | } 293 | } 294 | } 295 | ) 296 | } 297 | runOnUiThread { 298 | developersView.removeAllViews() 299 | for (view in tempViewList) { 300 | developersView.addView(view) 301 | } 302 | } 303 | } catch (e: Exception) { 304 | if (e.toString() 305 | .contains("Unable to resolve host \"api.github.com\"") 306 | ) { 307 | Snackbar.make( 308 | window.decorView, 309 | "无法连接到GitHub服务器, 请检查网络", 310 | Snackbar.LENGTH_LONG 311 | ).show() 312 | } else { 313 | e.printStackTrace() 314 | XposedBridge.log(e) 315 | } 316 | } 317 | } 318 | } 319 | } else { 320 | tipView?.text = "暂无" 321 | } 322 | } 323 | } catch (e: Exception) { 324 | e.printStackTrace() 325 | } 326 | } 327 | 328 | 329 | private fun changeUpdateChannel(context: Context) { 330 | val picURL = when (todayBingPicUrl) { 331 | "" -> null 332 | else -> todayBingPicUrl 333 | } 334 | var bottomBar: MaterialDialog? = null 335 | runOnMainThread { 336 | val linearLayout = LinearLayout(context).apply { 337 | layoutParams = LinearLayout.LayoutParams( 338 | LinearLayout.LayoutParams.MATCH_PARENT, 339 | LinearLayout.LayoutParams.WRAP_CONTENT 340 | ) 341 | orientation = LinearLayout.VERTICAL 342 | } 343 | val btnOfficial = ViewUtils_Kotlin.createBigButton( 344 | context, R.drawable.ic_baseline_double_arrow_24, 345 | R.color.teal_200, listOf("官方"), MyApplication.dailyBingPaper, R.color.white 346 | ) { 347 | context.getSharedPreferences("update_channel", Context.MODE_PRIVATE).edit() 348 | .apply { 349 | putString("username", "CoolestEnoch") 350 | putString("repo", "MIUI_Tools") 351 | commit() 352 | } 353 | bottomBar?.cancel() 354 | Unit 355 | } 356 | val btnLsp = ViewUtils_Kotlin.createBigButton( 357 | context, R.drawable.ic_baseline_double_arrow_24, 358 | R.color.teal_200, listOf("LSPosed"), MyApplication.dailyBingPaper, R.color.white 359 | ) { 360 | context.getSharedPreferences("update_channel", Context.MODE_PRIVATE).edit() 361 | .apply { 362 | putString("username", "Xposed-Modules-Repo") 363 | putString("repo", "com.coolest.toolbox") 364 | commit() 365 | } 366 | bottomBar?.cancel() 367 | Unit 368 | } 369 | linearLayout.addView(btnOfficial) 370 | linearLayout.addView(btnLsp) 371 | bottomBar = ViewUtils_Kotlin.getBigCardFromBottom(context, "更新渠道", linearLayout) 372 | bottomBar?.show() 373 | } 374 | 375 | 376 | } 377 | 378 | 379 | } -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/GithubUpdateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.util.Log 6 | import android.widget.LinearLayout 7 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout 8 | import com.coolest.toolbox.R 9 | import com.github.kyuubiran.ezxhelper.utils.runOnMainThread 10 | import com.google.android.material.snackbar.Snackbar 11 | import de.robv.android.xposed.XposedBridge 12 | import okhttp3.OkHttpClient 13 | import okhttp3.Request 14 | import org.json.JSONArray 15 | import org.json.JSONObject 16 | import java.text.DecimalFormat 17 | import java.util.* 18 | import kotlin.concurrent.thread 19 | import kotlin.math.min 20 | 21 | 22 | /** 23 | * 结构: 24 | * 版本号Tag 25 | * 描述 26 | * 资源列表: 列表[文件名 - 下载链接 - 创建时间] 27 | */ 28 | class Release( 29 | val relTag: String, 30 | val relName: String, 31 | val relDescription: String, 32 | val relTime: String, 33 | val relAssets: List> 34 | ) 35 | 36 | /** 37 | * 有更新就弹窗, 没更新就弹SnackBar提示是最新版本 38 | */ 39 | fun checkUpdate( 40 | currentVersion: String, 41 | swipeRefreshLayout: SwipeRefreshLayout, 42 | githubUserName: String, 43 | githubRepoName: String 44 | ) { 45 | swipeRefreshLayout.isRefreshing = true 46 | Snackbar.make(swipeRefreshLayout.rootView, "正在检查更新...", Snackbar.LENGTH_LONG).show() 47 | val context = swipeRefreshLayout.rootView.context 48 | //开始检查更新 49 | 50 | thread { 51 | try { 52 | //调用github API获取json 53 | var githubApiResponse: String? = "" 54 | val client = OkHttpClient() 55 | val request = Request.Builder() 56 | .url("https://api.github.com/repos/$githubUserName/$githubRepoName/releases/latest") 57 | .build() 58 | val response = client.newCall(request).execute() 59 | githubApiResponse = response.body?.string() 60 | Log.e("github repo", "$githubApiResponse") 61 | 62 | //解析json获取release信息 63 | val releaseObjectJson = JSONObject(githubApiResponse) 64 | if (releaseObjectJson.has("message") && releaseObjectJson.getString("message") 65 | .equals("Not Found") 66 | ) { 67 | //没有最新release或没有上传 68 | Snackbar.make( 69 | swipeRefreshLayout.rootView, 70 | "没有找到新版本, 可能是还没有上传?", 71 | Snackbar.LENGTH_LONG 72 | ).show() 73 | } else { 74 | val tag = releaseObjectJson.getString("tag_name") 75 | if (!needToUpdate(currentVersion, tag.substring(1))) { 76 | Snackbar.make(swipeRefreshLayout.rootView, "已是最新版本", Snackbar.LENGTH_LONG) 77 | .show() 78 | } else { 79 | val name = releaseObjectJson.getString("name") 80 | val description = "\n${releaseObjectJson.getString("body")}" 81 | val relTime = adjustTime(releaseObjectJson.getString("published_at")) 82 | val fileList = LinkedList>() 83 | //获取文件信息 84 | val filesJsonArray = releaseObjectJson.getJSONArray("assets") 85 | for (i in 0 until filesJsonArray.length()) { 86 | //获取单个文件信息 87 | val fileJsonObject = filesJsonArray.getJSONObject(i) 88 | val fileName = fileJsonObject.getString("name") 89 | val downloadUrl = fileJsonObject.getString("browser_download_url") 90 | val time = adjustTime(fileJsonObject.getString("updated_at")) 91 | val size = adjustFileSize(fileJsonObject.getLong("size")) 92 | val fileInfo = LinkedList().apply { 93 | add(fileName) 94 | add(downloadUrl) 95 | add(time) 96 | add(size) 97 | } 98 | fileList.add(fileInfo) 99 | } 100 | val latestRelease = Release(tag, name, description, relTime, fileList) 101 | 102 | //从latestRelease转换为底栏弹出的更新 103 | //最外层的LinearLayout 104 | val baseLinearLayout = LinearLayout(context).apply { 105 | layoutParams = LinearLayout.LayoutParams( 106 | LinearLayout.LayoutParams.MATCH_PARENT, 107 | LinearLayout.LayoutParams.WRAP_CONTENT 108 | ) 109 | orientation = LinearLayout.VERTICAL 110 | } 111 | //往最外层的LinearLayout里塞小卡片 112 | //一个release 113 | val cardView = ViewUtils_Kotlin.createBigButton( 114 | context, null, R.color.item_card_bg, 115 | listOf( 116 | latestRelease.relName, 117 | latestRelease.relTag, 118 | latestRelease.relTime, 119 | latestRelease.relDescription 120 | ), 121 | null, 122 | R.color.white 123 | ) {//点击release卡片后弹出一个新的小卡片, 上面记载着所有的文件 124 | val relLinearLayout = LinearLayout(context).apply { 125 | layoutParams = LinearLayout.LayoutParams( 126 | LinearLayout.LayoutParams.MATCH_PARENT, 127 | LinearLayout.LayoutParams.WRAP_CONTENT 128 | ) 129 | orientation = LinearLayout.VERTICAL 130 | } 131 | //从assets列表里取出所有文件清单 132 | for (file in latestRelease.relAssets) { 133 | //拿到单个文件 134 | val fileCard = ViewUtils_Kotlin.createBigButton( 135 | context, null, R.color.item_card_bg, 136 | listOf(file[0], file[2], file[3]), 137 | null, 138 | R.color.white 139 | ) { 140 | context.startActivity(Intent().apply { 141 | action = "android.intent.action.VIEW" 142 | data = Uri.parse(file[1]) 143 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 144 | }) 145 | } 146 | relLinearLayout.addView(fileCard) 147 | } 148 | runOnMainThread { 149 | ViewUtils_Kotlin.getBigCardFromBottom(context, "文件", relLinearLayout) 150 | .show() 151 | } 152 | 153 | Unit 154 | } 155 | baseLinearLayout.addView(cardView) 156 | runOnMainThread { 157 | ViewUtils_Kotlin.getBigCardFromBottom( 158 | context, 159 | "发现新版本! 当前版本:$currentVersion", 160 | baseLinearLayout 161 | ) 162 | .show() 163 | } 164 | } 165 | } 166 | } catch (e: Exception) { 167 | if (e.toString().contains("Unable to resolve host \"api.github.com\"")) { 168 | Snackbar.make( 169 | swipeRefreshLayout.rootView, 170 | "无法连接到GitHub服务器, 请检查网络", 171 | Snackbar.LENGTH_LONG 172 | ).show() 173 | } else { 174 | e.printStackTrace() 175 | XposedBridge.log(e) 176 | } 177 | } 178 | //检查更新完成, 结束刷新动画 179 | runOnMainThread { 180 | swipeRefreshLayout.isRefreshing = false 181 | } 182 | } 183 | 184 | // ViewUtils_Kotlin.codeNotDone(swipeRefreshLayout.context) 185 | } 186 | 187 | fun showAllRelease( 188 | swipeRefreshLayout: SwipeRefreshLayout, 189 | title: String?, 190 | githubUserName: String, 191 | githubRepoName: String 192 | ) { 193 | swipeRefreshLayout.isRefreshing = true 194 | Snackbar.make(swipeRefreshLayout.rootView, "正在查询历史版本...", Snackbar.LENGTH_LONG).show() 195 | val context = swipeRefreshLayout.rootView.context 196 | //开始检查更新 197 | 198 | thread { 199 | try { 200 | val releaseList = mutableListOf() 201 | 202 | //调用github API获取json 203 | var githubApiResponse: String? = "" 204 | val client = OkHttpClient() 205 | val request = Request.Builder() 206 | .url("https://api.github.com/repos/$githubUserName/$githubRepoName/releases") 207 | .build() 208 | val response = client.newCall(request).execute() 209 | githubApiResponse = response.body?.string() 210 | Log.e("github repo", "$githubApiResponse") 211 | 212 | //解析json获取所有release并存入releaseList 213 | //releaseList首元素为最新release 214 | val responseArray = JSONArray(githubApiResponse) 215 | if (responseArray.length() == 0) { 216 | Snackbar.make( 217 | swipeRefreshLayout.rootView, 218 | "没有找到任何历史版本, 可能是还没上传?", 219 | Snackbar.LENGTH_LONG 220 | ).show() 221 | } else { 222 | //所有的release 223 | for (i in 0 until responseArray.length()) { 224 | //一个release 225 | val relObject = responseArray.get(i) as JSONObject 226 | val tag = relObject.getString("tag_name") 227 | val name = relObject.getString("name") 228 | val description = "\n${relObject.getString("body")}" 229 | val relTime = adjustTime(relObject.getString("published_at")) 230 | val assetsList = LinkedList>() 231 | val assetsArray = relObject.getJSONArray("assets") 232 | //一个release里所有文件 233 | for (j in 0 until assetsArray.length()) { 234 | //一个文件 235 | val assetsObj = assetsArray.get(j) as JSONObject 236 | val fileName = assetsObj.getString("name") 237 | val downloadURL = assetsObj.getString("browser_download_url") 238 | val time = adjustTime(assetsObj.getString("updated_at")) 239 | val size = adjustFileSize(assetsObj.getLong("size")) 240 | assetsList.add(LinkedList().apply { 241 | add(fileName) 242 | add(downloadURL) 243 | add(time) 244 | add(size) 245 | }) 246 | } 247 | releaseList.add(Release(tag, name, description, relTime, assetsList)) 248 | } 249 | 250 | //从releaseList转换为底栏弹出的更新列表 251 | //最外层的LinearLayout 252 | val baseLinearLayout = LinearLayout(context).apply { 253 | layoutParams = LinearLayout.LayoutParams( 254 | LinearLayout.LayoutParams.MATCH_PARENT, 255 | LinearLayout.LayoutParams.WRAP_CONTENT 256 | ) 257 | orientation = LinearLayout.VERTICAL 258 | } 259 | //往最外层的LinearLayout里塞小卡片 260 | for (release in releaseList) { 261 | //一个release 262 | val cardView = ViewUtils_Kotlin.createBigButton( 263 | context, null, R.color.item_card_bg, 264 | listOf( 265 | release.relName, 266 | release.relTag, 267 | release.relTime, 268 | release.relDescription 269 | ), 270 | null, 271 | R.color.white 272 | ) {//点击release卡片后弹出一个新的小卡片, 上面记载着所有的文件 273 | val relLinearLayout = LinearLayout(context).apply { 274 | layoutParams = LinearLayout.LayoutParams( 275 | LinearLayout.LayoutParams.MATCH_PARENT, 276 | LinearLayout.LayoutParams.WRAP_CONTENT 277 | ) 278 | orientation = LinearLayout.VERTICAL 279 | } 280 | //从assets列表里取出所有文件清单 281 | for (file in release.relAssets) { 282 | //拿到单个文件 283 | val fileCard = ViewUtils_Kotlin.createBigButton( 284 | context, null, R.color.item_card_bg, 285 | listOf(file[0], file[2], file[3]), 286 | null, 287 | R.color.white 288 | ) { 289 | context.startActivity(Intent().apply { 290 | action = "android.intent.action.VIEW" 291 | data = Uri.parse(file[1]) 292 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 293 | }) 294 | } 295 | relLinearLayout.addView(fileCard) 296 | } 297 | runOnMainThread { 298 | ViewUtils_Kotlin.getBigCardFromBottom(context, "文件", relLinearLayout) 299 | .show() 300 | } 301 | 302 | Unit 303 | } 304 | baseLinearLayout.addView(cardView) 305 | } 306 | runOnMainThread { 307 | try { 308 | ViewUtils_Kotlin.getBigCardFromBottom( 309 | context, when (title) { 310 | null -> "所有版本" 311 | else -> title 312 | }, baseLinearLayout 313 | ) 314 | .show() 315 | } catch (e: Exception) { 316 | e.printStackTrace() 317 | XposedBridge.log(e) 318 | } 319 | } 320 | } 321 | 322 | } catch (e: Exception) { 323 | if (e.toString().contains("Unable to resolve host \"api.github.com\"")) { 324 | Snackbar.make( 325 | swipeRefreshLayout.rootView, 326 | "无法连接到GitHub服务器, 请检查网络", 327 | Snackbar.LENGTH_LONG 328 | ).show() 329 | } else { 330 | e.printStackTrace() 331 | XposedBridge.log(e) 332 | } 333 | } 334 | //检查更新完成, 结束刷新动画 335 | runOnMainThread { 336 | swipeRefreshLayout.isRefreshing = false 337 | } 338 | } 339 | 340 | // ViewUtils_Kotlin.codeNotDone(swipeRefreshLayout.context) 341 | } 342 | 343 | fun adjustFileSize(size: Long): String { 344 | if (size <= 0) return "0" 345 | val units = arrayOf("B", "KB", "MB", "GB", "TB") 346 | val digitGroups = (Math.log10(size.toDouble()) / Math.log10(1024.0)).toInt() 347 | val ret = DecimalFormat("#,##0.#").format(size / Math.pow(1024.0, digitGroups.toDouble())) 348 | .toString() + " " + units[digitGroups] 349 | return "文件大小: $ret" 350 | } 351 | 352 | fun adjustTime(githubTime: String): String { 353 | val year = githubTime.substring(0, 4) 354 | val month = githubTime.substring(6, 7) 355 | val day = githubTime.substring(8, 10) 356 | val hour = githubTime.substring(11, 13) 357 | val min = githubTime.substring(14, 16) 358 | val sec = githubTime.substring(17, 19) 359 | 360 | val calendar = Calendar.getInstance() 361 | calendar.set(year.toInt(), month.toInt(), day.toInt(), hour.toInt(), min.toInt(), sec.toInt()) 362 | calendar.add(Calendar.HOUR, 8) 363 | return "发布于: ${calendar.get(Calendar.YEAR)}年${calendar.get(Calendar.MONTH)}月${ 364 | calendar.get( 365 | Calendar.DATE 366 | ) 367 | }日 ${calendar.get(Calendar.HOUR_OF_DAY)}时${calendar.get(Calendar.MINUTE)}分${ 368 | calendar.get( 369 | Calendar.SECOND 370 | ) 371 | }秒" 372 | 373 | 374 | /*return "发布于: ${year}年${month}月${day}日 ${hour}时${min}分${sec}秒"*/ 375 | 376 | // 2022-03-20T08:24:33Z 377 | /*val timeStampUTC = transToTimeStamp(githubTime) 378 | val timeStampChn = timeStampUTC + 3600 * 8 379 | 380 | return "发布于: ${transToString(timeStampChn)}"*/ 381 | } 382 | 383 | 384 | fun needToUpdate(currentVersion: String, remoteVersion: String): Boolean { 385 | /* val versionArray1 = currentVersion.split("\\.") 386 | val versionArray2 = remoteVersion.split("\\.") 387 | var idx = 0 388 | val minLength = min(versionArray1.size, versionArray2.size) 389 | var diff = 0 390 | while (idx < minLength 391 | && (versionArray1[idx].length == versionArray2[idx].length) 392 | && (versionArray1[idx].compareTo(versionArray2[idx]) == 0) 393 | ) { 394 | ++idx 395 | } 396 | when (diff) { 397 | 0 -> versionArray1.size - versionArray2.size 398 | else -> diff 399 | } 400 | return diff <= 0*/ 401 | 402 | var ret = false 403 | try { 404 | ret = currentVersion.toDouble() < remoteVersion.toDouble() 405 | } catch (e: java.lang.Exception) { 406 | e.printStackTrace() 407 | XposedBridge.log(e) 408 | } 409 | return ret 410 | } -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/utils/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.utils; 2 | 3 | 4 | import android.app.Activity; 5 | import android.content.ComponentName; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Typeface; 10 | import android.util.DisplayMetrics; 11 | import android.util.TypedValue; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.WindowManager; 16 | import android.widget.ImageView; 17 | import android.widget.LinearLayout; 18 | import android.widget.TextView; 19 | 20 | import com.afollestad.materialdialogs.LayoutMode; 21 | import com.afollestad.materialdialogs.MaterialDialog; 22 | import com.afollestad.materialdialogs.bottomsheets.BottomSheet; 23 | import com.afollestad.materialdialogs.customview.DialogCustomViewExtKt; 24 | import com.bumptech.glide.Glide; 25 | import com.coolest.toolbox.R; 26 | import com.google.android.material.card.MaterialCardView; 27 | import com.google.android.material.shape.CornerFamily; 28 | import com.google.android.material.textfield.TextInputEditText; 29 | import com.google.android.material.textfield.TextInputLayout; 30 | 31 | import java.util.List; 32 | 33 | import kotlin.Unit; 34 | import kotlin.jvm.functions.Function1; 35 | 36 | public class ViewUtils { 37 | 38 | //以下内容需要在build.gradle里添加依赖: 39 | // api 'com.google.android.material:material:1.1.0-alpha06' 40 | // implementation 'com.afollestad.material-dialogs:core:3.2.1' 41 | // implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' 42 | 43 | 44 | public static int dp2px(Context context, float dpValue) { 45 | //获取屏幕分辨率 46 | final float scale = context.getResources().getDisplayMetrics().density; 47 | return (int) (dpValue * scale + 0.5f); 48 | } 49 | 50 | public static int px2dp(Context context, float pxValue) { 51 | //获取屏幕分辨率 52 | final float scale = context.getResources().getDisplayMetrics().density; 53 | return (int) (pxValue / scale + 0.5f); 54 | } 55 | 56 | //获取屏幕的宽度 57 | public static int getScreenWidth(Context ctx) { 58 | //从系统服务中获取窗口管理器 59 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 60 | DisplayMetrics dm = new DisplayMetrics(); 61 | //从默认显示器中获取显示参数保存到dm中 62 | wm.getDefaultDisplay().getMetrics(dm); 63 | return dm.widthPixels; 64 | } 65 | 66 | //获取屏幕的高度 67 | public static int getScreenHeight(Context ctx) { 68 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 69 | DisplayMetrics dm = new DisplayMetrics(); 70 | wm.getDefaultDisplay().getMetrics(dm); 71 | return dm.heightPixels; 72 | } 73 | 74 | //获取屏幕像素密度 75 | public static float getScreenDensity(Context ctx) { 76 | WindowManager wm = (WindowManager) ctx.getSystemService(Context.WINDOW_SERVICE); 77 | DisplayMetrics dm = new DisplayMetrics(); 78 | wm.getDefaultDisplay().getMetrics(dm); 79 | return dm.density; 80 | } 81 | 82 | /** 83 | * 计算线性布局的实际高度 84 | * @param child 85 | * @return 86 | */ 87 | public static float getRealHeight(View child){ 88 | LinearLayout linearLayout = (LinearLayout) child; 89 | //获取线性布局的参数 90 | ViewGroup.LayoutParams params = linearLayout.getLayoutParams(); 91 | if(params==null){ 92 | params=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 93 | } 94 | //获取布局参数里的参数规则 95 | int widthSpec = ViewGroup.getChildMeasureSpec(0,0,params.width); 96 | int heightSpec; 97 | if(params.height>0){ 98 | //高度大于0说明这是明确的dp值 99 | //按照精确值的情况计算高度规格 100 | heightSpec=View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 101 | }else{ 102 | //MATCH_PARENT = -1, WRAP_CONTENT = -2进入此分支 103 | //按照不确定的情况计算高度规则 104 | heightSpec=View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 105 | } 106 | //重新进行线性布局的宽高测量 107 | linearLayout.measure(widthSpec,heightSpec); 108 | //获取并返回线性布局测量后的高度值,用getMeasuredWidth方法获得宽度数值 109 | return linearLayout.getMeasuredHeight(); 110 | } 111 | 112 | /** 113 | * 拖更弹窗 114 | * @param context 115 | */ 116 | public static void codeNotDone(Context context){ 117 | MaterialDialog dialog = new MaterialDialog(context, new BottomSheet(LayoutMode.WRAP_CONTENT)); 118 | dialog.title(null, "啊哦"); 119 | DialogCustomViewExtKt.customView(dialog, R.layout.still_not_done, null, true, true, true, true); 120 | dialog.positiveButton(null, "确定", new Function1() { 121 | @Override 122 | public Unit invoke(MaterialDialog materialDialog) { 123 | Activity a = (Activity) context; 124 | //判断调用此方法的是否是主界面 125 | PackageManager packageManager = a.getApplication().getPackageManager(); 126 | Intent intent = packageManager.getLaunchIntentForPackage(a.getPackageName()); 127 | ComponentName launchComponentName = intent.getComponent(); 128 | ComponentName componentName = a.getComponentName(); 129 | if(componentName.toString().equals(launchComponentName.toString())){ 130 | // Log.i("min77",componentName.getClassName()+"是第一个启动的activity"); 131 | }else { 132 | // Log.i("min77",componentName.getClassName()+"不是第一个启动的activity"); 133 | a.finish(); 134 | } 135 | 136 | return null; 137 | } 138 | }); 139 | dialog.show(); 140 | } 141 | 142 | /** 143 | * 从屏幕底部弹出一张大卡片 144 | * 在build.gradle里添加以下依赖然后再使用: 145 | * implementation 'com.afollestad.material-dialogs:core:3.2.1' 146 | * implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0' 147 | * 148 | * @param context 149 | * @return 150 | */ 151 | public static MaterialDialog getBigCardFromBottom(Context context, String title, View view) { 152 | MaterialDialog dialog = new MaterialDialog(context, new BottomSheet(LayoutMode.WRAP_CONTENT)); 153 | dialog.title(null, title); 154 | DialogCustomViewExtKt.customView(dialog, null, view, true, true, true, true); 155 | dialog.positiveButton(null, "确定", new Function1() { 156 | @Override 157 | public Unit invoke(MaterialDialog materialDialog) { 158 | Activity a = (Activity) context; 159 | // a.finish(); 160 | return null; 161 | } 162 | }); 163 | return dialog; 164 | } 165 | 166 | 167 | 168 | /** 169 | * 创建一张Material Design风格卡片,其中StringList的首项将作为标题,会被加粗 170 | * 171 | * @param context 这个不用解释了吧2333 172 | * @param bg_color_id 背景颜色 173 | * @param icon_url 左边提示图标的资源url 174 | * @param strList 首项作为标题,会被加粗 175 | * @return 176 | */ 177 | public static MaterialCardView createCardView(Context context, int bg_color_id, String icon_url, List strList) { 178 | if (strList == null) { 179 | return null; 180 | } 181 | //创建卡片 - mainCard 182 | MaterialCardView mainCard = new MaterialCardView(context); 183 | LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 184 | cardParams.setMargins(ViewUtils.dp2px(context, 18), ViewUtils.dp2px(context, 10), ViewUtils.dp2px(context, 18), 0); 185 | mainCard.setLayoutParams(cardParams); 186 | mainCard.setBackgroundColor(context.getResources().getColor(bg_color_id)); 187 | mainCard.setClickable(true); 188 | mainCard.setFocusable(true); 189 | mainCard.setShapeAppearanceModel(mainCard.getShapeAppearanceModel() 190 | .toBuilder() 191 | .setBottomLeftCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 192 | .setBottomRightCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 193 | .setTopLeftCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 194 | .setTopRightCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 195 | .build() 196 | ); 197 | //创建卡片内的框架LinearLayout - frameworkLinearLayout 198 | LinearLayout frameworkLinearLayout = new LinearLayout(context); 199 | LinearLayout.LayoutParams frameworkLinearLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 200 | frameworkLinearLayout.setLayoutParams(frameworkLinearLayoutParams); 201 | frameworkLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 202 | //创建卡片内的图标ImageView - icon 203 | ImageView icon = new ImageView(context); 204 | LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(ViewUtils.dp2px(context, 28), ViewUtils.dp2px(context, 28)); 205 | iconParams.gravity = Gravity.CENTER_VERTICAL; 206 | iconParams.setMargins(ViewUtils.dp2px(context, 26), 0, ViewUtils.dp2px(context, 20), 0);//卡片图标左右边距 207 | icon.setLayoutParams(iconParams); 208 | Glide.with(context).load(icon_url).into(icon); 209 | //创建在图标右边的LinearLayout - infoLinearLayout 210 | LinearLayout infoLinearLayout = new LinearLayout(context); 211 | LinearLayout.LayoutParams infoLinearLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 212 | infoLinearLayout.setLayoutParams(infoLinearLayoutParams); 213 | infoLinearLayout.setOrientation(LinearLayout.VERTICAL); 214 | infoLinearLayout.setPadding(0, ViewUtils.dp2px(context, 25), 0, ViewUtils.dp2px(context, 25)); 215 | //往infoLinearLayout里添加文本框 216 | boolean firstEle = true;//实现第一个文本框加粗 217 | for (String str : strList) { 218 | TextView tv = new TextView(context); 219 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 220 | tv.setLayoutParams(layoutParams); 221 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); 222 | tv.setTextColor(context.getResources().getColor(R.color.white)); 223 | if (firstEle) {//实现第一个文本框加粗 224 | firstEle = false; 225 | tv.setTypeface(null, Typeface.BOLD); 226 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 22); 227 | tv.setPadding(0, 0, 0, ViewUtils.dp2px(context, 5)); 228 | } else { 229 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); 230 | } 231 | tv.setAllCaps(false); 232 | tv.setText(str); 233 | infoLinearLayout.addView(tv); 234 | } 235 | //添加视图 236 | frameworkLinearLayout.addView(icon); 237 | frameworkLinearLayout.addView(infoLinearLayout); 238 | mainCard.addView(frameworkLinearLayout); 239 | return mainCard; 240 | } 241 | 242 | /** 243 | * 创建一张Material Design风格卡片,其中StringList的首项将作为标题,会被加粗 244 | * 245 | * @param context 这个不用解释了吧2333 246 | * @param bg_color_id 背景颜色 247 | * @param icon_id 左边提示图标的资源id 248 | * @param strList 首项作为标题,会被加粗 249 | * @return 250 | */ 251 | public static MaterialCardView createCardView(Context context, int bg_color_id, int icon_id, List strList) { 252 | if (strList == null) { 253 | return null; 254 | } 255 | //创建卡片 - mainCard 256 | MaterialCardView mainCard = new MaterialCardView(context); 257 | LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 258 | cardParams.setMargins(ViewUtils.dp2px(context, 18), ViewUtils.dp2px(context, 10), ViewUtils.dp2px(context, 18), 0); 259 | mainCard.setLayoutParams(cardParams); 260 | mainCard.setBackgroundColor(context.getResources().getColor(bg_color_id)); 261 | mainCard.setClickable(true); 262 | mainCard.setFocusable(true); 263 | mainCard.setShapeAppearanceModel(mainCard.getShapeAppearanceModel() 264 | .toBuilder() 265 | .setBottomLeftCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 266 | .setBottomRightCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 267 | .setTopLeftCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 268 | .setTopRightCorner(CornerFamily.ROUNDED, (float) ViewUtils.dp2px(context, 10)) 269 | .build() 270 | ); 271 | //创建卡片内的框架LinearLayout - frameworkLinearLayout 272 | LinearLayout frameworkLinearLayout = new LinearLayout(context); 273 | LinearLayout.LayoutParams frameworkLinearLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 274 | frameworkLinearLayout.setLayoutParams(frameworkLinearLayoutParams); 275 | frameworkLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 276 | //创建卡片内的图标ImageView - icon 277 | ImageView icon = new ImageView(context); 278 | LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(ViewUtils.dp2px(context, 28), ViewUtils.dp2px(context, 28)); 279 | iconParams.gravity = Gravity.CENTER_VERTICAL; 280 | iconParams.setMargins(ViewUtils.dp2px(context, 26), 0, ViewUtils.dp2px(context, 20), 0);//卡片图标左右边距 281 | icon.setLayoutParams(iconParams); 282 | icon.setBackground(context.getResources().getDrawable(icon_id)); 283 | //创建在图标右边的LinearLayout - infoLinearLayout 284 | LinearLayout infoLinearLayout = new LinearLayout(context); 285 | LinearLayout.LayoutParams infoLinearLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 286 | infoLinearLayout.setLayoutParams(infoLinearLayoutParams); 287 | infoLinearLayout.setOrientation(LinearLayout.VERTICAL); 288 | infoLinearLayout.setPadding(0, ViewUtils.dp2px(context, 25), 0, ViewUtils.dp2px(context, 25)); 289 | //往infoLinearLayout里添加文本框 290 | boolean firstEle = true;//实现第一个文本框加粗 291 | for (String str : strList) { 292 | TextView tv = new TextView(context); 293 | LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 294 | tv.setLayoutParams(layoutParams); 295 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); 296 | tv.setTextColor(context.getResources().getColor(R.color.white)); 297 | if (firstEle) {//实现第一个文本框加粗 298 | firstEle = false; 299 | tv.setTypeface(null, Typeface.BOLD); 300 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 22); 301 | tv.setPadding(0, 0, 0, ViewUtils.dp2px(context, 5)); 302 | } else { 303 | tv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); 304 | } 305 | tv.setAllCaps(false); 306 | tv.setText(str); 307 | infoLinearLayout.addView(tv); 308 | } 309 | //添加视图 310 | frameworkLinearLayout.addView(icon); 311 | frameworkLinearLayout.addView(infoLinearLayout); 312 | mainCard.addView(frameworkLinearLayout); 313 | return mainCard; 314 | } 315 | 316 | /** 317 | * 创建Material Designed风格大按钮 318 | * 319 | * @param context 这个不用解释了吧2333 320 | * @param icon_id 左边提示图标的资源id 321 | * @param color_id 背景颜色 322 | * @param str 按钮文本 323 | * @return 324 | */ 325 | public static MaterialCardView createBigButton(Context context, int icon_id, int color_id, String str) { 326 | //创建大卡片 - cardMain 327 | MaterialCardView cardMain = new MaterialCardView(context); 328 | LinearLayout.LayoutParams cardMainLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 329 | cardMainLayoutParams.setMargins(dp2px(context, 18), dp2px(context, 10), dp2px(context, 18), 0); 330 | cardMain.setLayoutParams(cardMainLayoutParams); 331 | cardMain.setCardBackgroundColor(context.getResources().getColor(color_id)); 332 | // cardMain.setCardBackgroundColor(0xFF018786); 333 | cardMain.setFocusable(true); 334 | cardMain.setClickable(true); 335 | //创建框架LinearLayout - frameworkLinearLayout 336 | LinearLayout frameworkLinearLayout = new LinearLayout(context); 337 | LinearLayout.LayoutParams frameworkLinearLayoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 338 | frameworkLinearLayout.setLayoutParams(frameworkLinearLayoutParams); 339 | frameworkLinearLayout.setOrientation(LinearLayout.HORIZONTAL); 340 | //创建小的图标 - icon 341 | ImageView icon = new ImageView(context); 342 | LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(dp2px(context, 28), dp2px(context, 28)); 343 | iconParams.gravity = Gravity.CENTER_VERTICAL; 344 | iconParams.setMargins(dp2px(context, 20), 0, dp2px(context, 16), 0); 345 | icon.setLayoutParams(iconParams); 346 | // icon.setBackground(context.getResources().getDrawable(icon_id));//弃用了,用下面这句话 347 | icon.setBackgroundResource(icon_id); 348 | //创建标题 - title 349 | TextView title = new TextView(context); 350 | LinearLayout.LayoutParams tvParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 351 | title.setLayoutParams(tvParams); 352 | title.setPadding(0, dp2px(context, 21), 0, dp2px(context, 21)); 353 | title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); 354 | title.setTextColor(context.getResources().getColor(R.color.black)); 355 | // title.setTextAppearance(R.attr.textAppearanceHeadline6); 356 | title.setText(str); 357 | //添加布局 358 | frameworkLinearLayout.addView(icon); 359 | frameworkLinearLayout.addView(title); 360 | cardMain.addView(frameworkLinearLayout); 361 | return cardMain; 362 | } 363 | 364 | /** 365 | * 创建一个MD输入框 366 | * 367 | * @param width_ViewGroup_LayoutParams_WHAT 368 | * @param height_ViewGroup_LayoutParams_WHAT 369 | * @return 370 | */ 371 | public static TextInputLayout createTextInputDefault(Context context, int width_ViewGroup_LayoutParams_WHAT, int height_ViewGroup_LayoutParams_WHAT) { 372 | /** 373 | * width和height从ViewGroup.LayoutParams里选MATCH_PARENT或WRAP_CONTENT 374 | */ 375 | TextInputLayout inputLayout = new TextInputLayout(context, null, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox); 376 | LinearLayout.LayoutParams inputLayoutParams = new LinearLayout.LayoutParams(width_ViewGroup_LayoutParams_WHAT, height_ViewGroup_LayoutParams_WHAT); 377 | inputLayoutParams.setMargins(dp2px(context, 18), dp2px(context, 10), dp2px(context, 18), 0); 378 | inputLayout.setLayoutParams(inputLayoutParams); 379 | 380 | TextInputEditText inputEditText = new TextInputEditText(context); 381 | LinearLayout.LayoutParams inputEditTextParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 382 | inputEditText.setLayoutParams(inputEditTextParams); 383 | 384 | inputLayout.addView(inputEditText); 385 | return inputLayout; 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /code/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 20 | 21 | 24 | 25 | 29 | 30 | 37 | 38 | 47 | 48 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 69 | 70 | 71 | 72 | 85 | 86 | 91 | 92 | 104 | 105 | 122 | 123 | 124 | 125 | 130 | 131 | 132 | 137 | 138 | 151 | 152 | 165 | 166 | 178 | 179 | 191 | 192 | 193 | 194 | 195 | 200 | 201 | 208 | 209 | 221 | 222 | 231 | 232 | 241 | 242 | 251 | 252 | 253 | 259 | 260 | 270 | 271 | 280 | 281 | 285 | 286 | 291 | 292 | 293 | 294 | 299 | 300 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 332 | 333 | 338 | 339 | 351 | 352 | 369 | 370 | 371 | 372 | 377 | 378 | 379 | 384 | 385 | 398 | 399 | 412 | 413 | 426 | 427 | 428 | 429 | 430 | 435 | 436 | 441 | 442 | 454 | 455 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 483 | 484 | 492 | 493 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | -------------------------------------------------------------------------------- /code/app/src/main/java/com/coolest/toolbox/xposed/HookEntry.kt: -------------------------------------------------------------------------------- 1 | package com.coolest.toolbox.xposed 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.AndroidAppHelper 5 | import android.content.Context 6 | import android.content.res.XResources 7 | import android.view.View 8 | import com.coolest.toolbox.BuildConfig 9 | import com.coolest.toolbox.ui.CoolUtils 10 | import com.coolest.toolbox.utils.CoolConfigHelper 11 | import com.github.kyuubiran.ezxhelper.init.EzXHelperInit 12 | import com.github.kyuubiran.ezxhelper.utils.* 13 | import de.robv.android.xposed.* 14 | import de.robv.android.xposed.callbacks.XC_InitPackageResources 15 | import de.robv.android.xposed.callbacks.XC_LoadPackage 16 | 17 | class HookEntry : IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources { 18 | 19 | override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { 20 | EzXHelperInit.initZygote(startupParam) 21 | XposedBridge.log("FM: init zygote") 22 | } 23 | 24 | override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) { 25 | when (resparam.packageName) { 26 | "com.xiaomi.mirror" -> { 27 | try { 28 | val enabled = try { 29 | CoolConfigHelper.getJsonConfig( 30 | getContext(), 31 | "miui_plus_cust_pc_name_switch" 32 | ) as Boolean 33 | } catch (e: Exception) { 34 | false 35 | } 36 | if (enabled) { 37 | val computerTail = 38 | CoolConfigHelper.getJsonConfig(getContext(), "miui_plus_pc_tail") 39 | when (computerTail) { 40 | "" -> Unit 41 | else -> { 42 | XposedBridge.log("电脑小尾巴替换成功: $computerTail") 43 | resparam.res.setReplacement( 44 | "com.xiaomi.mirror", 45 | "string", 46 | "default_boss_terminal_name", 47 | computerTail 48 | ) 49 | } 50 | } 51 | } 52 | } catch (e: Exception) { 53 | XposedBridge.log(e) 54 | } 55 | } 56 | } 57 | } 58 | 59 | @SuppressLint("PrivateApi", "ObsoleteSdkInt", "NewApi") 60 | override fun handleLoadPackage(lparam: XC_LoadPackage.LoadPackageParam?) { 61 | if (lparam != null) { 62 | 63 | EzXHelperInit.initHandleLoadPackage(lparam) 64 | EzXHelperInit.setLogTag("Fuck Miui Xposed") 65 | EzXHelperInit.setToastTag("FM") 66 | 67 | when (lparam.packageName) { 68 | BuildConfig.APPLICATION_ID -> { 69 | try { 70 | XposedBridge.log("FM: Hook self succeed!") 71 | 72 | findMethod("com.coolest.toolbox.ui.MainActivity") { 73 | name == "isXposedActivated" && isNotStatic && returnType == Boolean::class.java 74 | }.hookMethod { 75 | before { 76 | XposedBridge.log("FM: detecting activated") 77 | } 78 | after { param -> 79 | XposedBridge.log("FM: set module active state to activated") 80 | param.result = true 81 | } 82 | } 83 | } catch (e: Exception) { 84 | XposedBridge.log(e) 85 | } 86 | } 87 | 88 | "android" -> { 89 | //miui-services.jar 90 | try { 91 | if (CoolUtils.isMIUI()) { 92 | findMethod("com.android.server.wm.MiuiFreeFormStackDisplayStrategy") { 93 | name == "getMaxMiuiFreeFormStackCount" && isNotStatic && returnType == Int::class.java 94 | }.hookMethod { 95 | before { lparam -> 96 | lparam.result = 256 97 | XposedBridge.log("[FM] 已将无限小窗个数改为${lparam.result}") 98 | } 99 | after { param -> 100 | 101 | } 102 | } 103 | } 104 | } catch (e: Exception) { 105 | XposedBridge.log("[FM] 无限小窗出现错误!") 106 | XposedBridge.log(e) 107 | } 108 | 109 | //services.jar 110 | try { 111 | if (CoolUtils.isMIUI()) { 112 | findMethod("com.android.server.wm.Task") { 113 | name == "isResizeable" && isNotStatic && returnType == Boolean::class.java 114 | }.hookMethod { 115 | before { lparam -> 116 | lparam.result = true 117 | XposedBridge.log("[FM] 强制设置所有程序允许小窗${lparam.result}") 118 | } 119 | after { param -> 120 | 121 | } 122 | } 123 | } 124 | } catch (e: Exception) { 125 | XposedBridge.log("[FM] 强制小窗出现错误!") 126 | XposedBridge.log(e) 127 | } 128 | 129 | //miui-framework.jar 130 | try { 131 | if (CoolUtils.isMIUI()) { 132 | findMethod("android.util.MiuiMultiWindowAdapter") { 133 | name == "getFreeformBlackList" && isStatic 134 | }.hookMethod { 135 | before { lparam -> 136 | lparam.result = listOf("") 137 | XposedBridge.log("[FM] 小窗黑名单=空列表") 138 | } 139 | after { param -> 140 | 141 | } 142 | } 143 | } 144 | } catch (e: Exception) { 145 | XposedBridge.log("[FM] 小窗黑名单出现错误!") 146 | XposedBridge.log(e) 147 | } 148 | 149 | //miui-framework.jar 150 | try { 151 | if (CoolUtils.isMIUI()) { 152 | findMethod("android.util.MiuiMultiWindowAdapter") { 153 | name == "getFreeformBlackListFromCloud" && isStatic 154 | }.hookMethod { 155 | before { lparam -> 156 | lparam.result = listOf("") 157 | XposedBridge.log("[FM] 小窗云控黑名单=空列表") 158 | } 159 | after { param -> 160 | 161 | } 162 | } 163 | } 164 | } catch (e: Exception) { 165 | XposedBridge.log("[FM] 小窗云控黑名单出现错误!") 166 | XposedBridge.log(e) 167 | } 168 | 169 | //miui-framework.jar 170 | try { 171 | if (CoolUtils.isMIUI()) { 172 | findMethod("android.util.MiuiMultiWindowUtils") { 173 | name == "supportFreeform" && isStatic && returnType == Boolean::class.java 174 | }.hookMethod { 175 | before { lparam -> 176 | XposedBridge.log("[FM] 云控设备不支持小窗 = false") 177 | lparam.result = true 178 | } 179 | after { param -> 180 | 181 | } 182 | } 183 | } 184 | } catch (e: Exception) { 185 | XposedBridge.log("[FM] 云控不支持小窗设备列表出现错误!") 186 | XposedBridge.log(e) 187 | } 188 | } 189 | 190 | // "com.miui.misound" -> hookMiSound() 191 | "com.miui.misound" -> try { 192 | if (CoolUtils.isMIUI()) { 193 | 194 | findMethod("com.miui.misound.util.Utils") { 195 | name == "isSupportHarmanKardon" && isStatic && returnType == Boolean::class.java 196 | }.hookMethod { 197 | before { lparam -> 198 | /*val jsonResult = 199 | CoolConfigHelper.getJsonConfig(getContext(),"harmanKardon_enabled") 200 | XposedBridge.log("FM: 哈曼卡顿启用状态: $jsonResult") 201 | lparam.result = try { 202 | jsonResult as Boolean 203 | } catch (e: Exception) { 204 | false 205 | }*/ 206 | lparam.result = true 207 | } 208 | after { param -> 209 | 210 | } 211 | } 212 | /*findMethod("com.miui.misound.util.Utils") { 213 | name == "isSupportDolbyDax" && isStatic && returnType == Boolean::class.java 214 | }.hookMethod { 215 | before { lparam -> 216 | *//*val jsonResult = when(CoolUtils.isPad()) { 217 | true -> true 218 | false -> CoolConfigHelper.getJsonConfig("dolby_enabled") 219 | }*//* 220 | *//*val jsonResult = CoolConfigHelper.getJsonConfig(getContext(),"dolby_enabled") 221 | XposedBridge.log("FM: 杜比全景声启用状态: $jsonResult") 222 | lparam.result = try { 223 | jsonResult as Boolean 224 | } catch (e: Exception) { 225 | false 226 | }*//* 227 | lparam.result = true 228 | } 229 | after { param -> 230 | 231 | } 232 | }*/ 233 | 234 | } 235 | } catch (e: Exception) { 236 | XposedBridge.log("[FM] 哈曼卡顿出现错误!") 237 | XposedBridge.log(e) 238 | } 239 | 240 | "com.miui.home" -> { 241 | if (CoolUtils.isMIUI()) { 242 | 243 | try { 244 | findMethod("com.miui.home.launcher.RecentsAndFSGestureUtils") { 245 | name == "canTaskEnterSmallWindow" && isStatic && returnType == Boolean::class.java 246 | }.hookMethod { 247 | before { param -> 248 | XposedBridge.log("[FM]: 允许进程进入小窗") 249 | param.result = true 250 | } 251 | after { param -> 252 | } 253 | } 254 | } catch (e: Exception) { 255 | XposedBridge.log(e) 256 | } 257 | 258 | try { 259 | findMethod("com.miui.home.launcher.RecentsAndFSGestureUtils") { 260 | name == "canTaskEnterMiniSmallWindow" && isStatic && returnType == Boolean::class.java 261 | }.hookMethod { 262 | before { 263 | XposedBridge.log("FM: detecting activated") 264 | } 265 | after { param -> 266 | XposedBridge.log("FM: set module active state to activated") 267 | param.result = true 268 | } 269 | } 270 | } catch (e: Exception) { 271 | XposedBridge.log(e) 272 | } 273 | } 274 | } 275 | 276 | "com.miui.securitycenter" -> { 277 | if (CoolUtils.isMIUI()) { 278 | val jsonResult = 279 | CoolConfigHelper.getJsonConfig( 280 | getContext(), 281 | "miui_secure_center_lock_100" 282 | ) 283 | when (jsonResult as Boolean) { 284 | true -> { 285 | //防止点击重新检测 286 | findMethod("com.miui.securityscan.ui.main.MainContentFrame") { 287 | name == "onClick" && parameterTypes[0] == View::class.java 288 | }.hookMethod { 289 | before { param -> param.result = null } 290 | } 291 | 292 | //锁定100分 293 | findMethod("com.miui.securityscan.scanner.ScoreManager") { 294 | name == "B" 295 | }.hookMethod { 296 | before { param -> param.result = 0 } 297 | } 298 | } 299 | false -> {} 300 | } 301 | } 302 | } 303 | 304 | "com.xiaomi.mirror" -> { 305 | XposedBridge.log("FM: MIUI+ app has found!") 306 | 307 | //解锁平板MIUI+ 308 | try { 309 | findMethod("com.xiaomi.mirror.utils.DeviceUtils") { 310 | name == "isPadDevice" && isStatic && returnType == Boolean::class.java 311 | }.hookMethod { 312 | before { 313 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.utils.DeviceUtils]") 314 | XposedBridge.log("FM: Found method @isPad in [com.xiaomi.mirror]") 315 | } 316 | after { param -> 317 | // XposedBridge.log("FM: com.xiaomi.mirror.utils.DeviceUtils.isPadDevice() returns false") 318 | val jsonResult = 319 | CoolConfigHelper.getJsonConfig( 320 | getContext(), 321 | "miui_plus_connect_pc" 322 | ) 323 | XposedBridge.log("FM: Hooked com.xiaomi.mirror @isPad, user's setting is $jsonResult") 324 | param.result = !try { 325 | jsonResult as Boolean 326 | } catch (e: Exception) { 327 | false 328 | } 329 | } 330 | } 331 | } catch (e: Exception) { 332 | XposedBridge.log(e) 333 | } 334 | 335 | //解锁平板MIUI+ 336 | //3.5.17c 337 | try { 338 | findMethod("com.xiaomi.mirror.device.DeviceUtils") { 339 | name == "isPadDevice" && isStatic && returnType == Boolean::class.java 340 | }.hookMethod { 341 | before { 342 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.utils.DeviceUtils]") 343 | XposedBridge.log("FM: Found method @isPad in [com.xiaomi.mirror] v3.5.17c") 344 | } 345 | after { param -> 346 | // XposedBridge.log("FM: com.xiaomi.mirror.utils.DeviceUtils.isPadDevice() returns false") 347 | val jsonResult = 348 | CoolConfigHelper.getJsonConfig( 349 | getContext(), 350 | "miui_plus_connect_pc" 351 | ) 352 | XposedBridge.log("FM: Hooked com.xiaomi.mirror @isPad, user's setting is $jsonResult v3.5.17c") 353 | param.result = !try { 354 | jsonResult as Boolean 355 | } catch (e: Exception) { 356 | false 357 | } 358 | } 359 | } 360 | } catch (e: Exception) { 361 | XposedBridge.log(e) 362 | } 363 | 364 | try { 365 | findMethod("com.xiaomi.onetrack.util.DeviceUtil") { 366 | name == "p" && isStatic && returnType == Boolean::class.java 367 | }.hookMethod { 368 | before { 369 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.utils.DeviceUtils]") 370 | XposedBridge.log("FM: Found method @isPad in [com.xiaomi.mirror]") 371 | } 372 | after { param -> 373 | // XposedBridge.log("FM: com.xiaomi.mirror.utils.DeviceUtils.isPadDevice() returns false") 374 | val jsonResult = 375 | CoolConfigHelper.getJsonConfig( 376 | getContext(), 377 | "miui_plus_connect_pc" 378 | ) 379 | XposedBridge.log("FM: Hooked com.xiaomi.mirror @isPad, user's setting is $jsonResult") 380 | param.result = !try { 381 | jsonResult as Boolean 382 | } catch (e: Exception) { 383 | false 384 | } 385 | } 386 | } 387 | } catch (e: Exception) { 388 | XposedBridge.log(e) 389 | } 390 | 391 | /* try { 392 | findMethod("com.xiaomi.mirror.ak") { 393 | name == "a" && returnType == Boolean::class.java 394 | }.hookMethod { 395 | before { 396 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.ak]") 397 | XposedBridge.log("FM: Found method @a in [com.xiaomi.mirror]") 398 | } 399 | after { param -> 400 | // XposedBridge.log("FM: com.xiaomi.mirror.ak.a() returns true") 401 | XposedBridge.log("FM: Hooked com.xiaomi.mirror @a") 402 | param.result = true 403 | } 404 | } 405 | } catch (e: Exception) { 406 | XposedBridge.log(e) 407 | }*/ 408 | 409 | //解锁不支持得的设备的MIUI+ 410 | try { 411 | findMethod("com.xiaomi.mirror.utils.SystemUtils") { 412 | name == "isModelSupport" && returnType == Boolean::class.java 413 | }.hookMethod { 414 | before { 415 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.utils.SystemUtils]") 416 | XposedBridge.log("FM: Found method @SystemUtils in [com.xiaomi.mirror]") 417 | } 418 | after { param -> 419 | // XposedBridge.log("FM: com.xiaomi.mirror.utils.SystemUtils.isModelSupport() returns true") 420 | XposedBridge.log("FM: Hooked com.xiaomi.mirror @SystemUtils!") 421 | param.result = true 422 | } 423 | } 424 | 425 | /* 426 | //====================================== 427 | //修改类中的变量值示例代码 428 | val field = findAllFields("com.miui.player.effect.dirac.DiracUtils", false){ 429 | true 430 | } 431 | lateinit var myObject: Any; 432 | lateinit var myField: Field; 433 | field.forEach { 434 | XposedBridge.log("[debug] " + it.name) 435 | 436 | if (it.name == "VAL_OFF") { 437 | myField = it 438 | myObject = it.get(it.javaClass)!! 439 | } 440 | } 441 | myField.set(myField.javaClass, false) 442 | //====================================== 443 | 444 | //====================================== 445 | //调用某个类中的方法 446 | val method = findMethod("com.miui.player.effect.dirac.DiracUtils"){ 447 | name == "isSupportEarcompensation" && isStatic && returnType == Boolean::class.java 448 | } 449 | val bool = method.invoke(Boolean::class.java)//这个方法返回值是boolean所以参数传的是Boolean::class.java 450 | //====================================== 451 | */ 452 | 453 | } catch (e: Exception) { 454 | XposedBridge.log(e) 455 | } 456 | 457 | //自定义电脑名 458 | try { 459 | val enabled = try { 460 | CoolConfigHelper.getJsonConfig( 461 | getContext(), 462 | "miui_plus_cust_pc_name_switch" 463 | ) as Boolean 464 | } catch (e: Exception) { 465 | false 466 | } 467 | if (enabled) { 468 | findMethod("com.xiaomi.mirror.MirrorAppService") { 469 | name == "getMasterName" && isNotStatic && returnType == String::class.java 470 | }.hookMethod { 471 | before { 472 | // XposedBridge.log("FM: Found method in [com.xiaomi.mirror.utils.SystemUtils]") 473 | XposedBridge.log("FM: Found method @masterName in [com.xiaomi.mirror]") 474 | } 475 | after { param -> 476 | // XposedBridge.log("FM: com.xiaomi.mirror.utils.SystemUtils.isModelSupport() returns true") 477 | val computerName = 478 | CoolConfigHelper.getJsonConfig( 479 | getContext(), 480 | "miui_plus_pc_name" 481 | ) 482 | XposedBridge.log("自定义电脑名 $enabled -> $computerName") 483 | param.result = when (computerName) { 484 | "" -> param.result 485 | else -> computerName 486 | } 487 | } 488 | } 489 | } 490 | } catch (e: Exception) { 491 | XposedBridge.log(e) 492 | } 493 | } 494 | 495 | "com.android.settings" -> { 496 | XposedBridge.log("FM: Settings Found!") 497 | 498 | try { 499 | findMethod("com.android.settings.connection.MiMirrorController") { 500 | name == "isMirrorSupported" && isNotStatic && returnType == Boolean::class.java 501 | }.hookMethod { 502 | before { 503 | // XposedBridge.log("FM: Found method in [com.android.settings.connection.MiMirrorController]") 504 | XposedBridge.log("FM: Found method in [com.android.settings]") 505 | } 506 | after { param -> 507 | // XposedBridge.log("FM: com.android.settings.connection.MiMirrorController.isMirrorSupport() returns true") 508 | XposedBridge.log("FM: Hooked com.android.settings!") 509 | param.result = true 510 | } 511 | } 512 | } catch (e: Exception) { 513 | XposedBridge.log(e) 514 | } 515 | } 516 | 517 | //平板流转手机 518 | /*"com.xiaomi.mi_connect_service" -> { 519 | XposedBridge.log("FM: mi_connect_service app has found!") 520 | 521 | try { 522 | findMethod("b.h.q.e2.q") { 523 | name == "p" && isStatic && returnType == Boolean::class.java 524 | }.hookMethod { 525 | before { 526 | XposedBridge.log("FM: Found method in [mi_connect_service {p}]") 527 | } 528 | after { param -> 529 | val jsonResult = 530 | CoolConfigHelper.getJsonConfig("miui_plus_connect_pc") 531 | XposedBridge.log("FM: Hooked com.xiaomi.mi_connect_service {p} @isPad, user's setting is $jsonResult") 532 | param.result = !try { 533 | jsonResult as Boolean 534 | } catch (e: Exception) { 535 | false 536 | } 537 | } 538 | } 539 | findMethod("b.h.q.e2.q") { 540 | name == "q" && isStatic && returnType == Boolean::class.java 541 | }.hookMethod { 542 | before { 543 | XposedBridge.log("FM: Found method in [mi_connect_service {q}]") 544 | } 545 | after { param -> 546 | val jsonResult = 547 | CoolConfigHelper.getJsonConfig("miui_plus_connect_pc") 548 | XposedBridge.log("FM: Hooked com.xiaomi.mi_connect_service {q} @isPad, user's setting is $jsonResult") 549 | param.result = !try { 550 | jsonResult as Boolean 551 | } catch (e: Exception) { 552 | false 553 | } 554 | } 555 | } 556 | findMethod("b.h.e.e") { 557 | name == "e" && isStatic && returnType == Boolean::class.java 558 | }.hookMethod { 559 | before { 560 | XposedBridge.log("FM: Found method in [mi_connect_service {e}]") 561 | } 562 | after { param -> 563 | val jsonResult = 564 | CoolConfigHelper.getJsonConfig("miui_plus_connect_pc") 565 | XposedBridge.log("FM: Hooked com.xiaomi.mi_connect_service {e} @isPad, user's setting is $jsonResult") 566 | param.result = !try { 567 | jsonResult as Boolean 568 | } catch (e: Exception) { 569 | false 570 | } 571 | } 572 | } 573 | findMethod("com.xiaomi.onetrack.h.h") { 574 | name == "o" && isStatic && returnType == Boolean::class.java 575 | }.hookMethod { 576 | before { 577 | XposedBridge.log("FM: Found method in [mi_connect_service {o}]") 578 | } 579 | after { param -> 580 | val jsonResult = 581 | CoolConfigHelper.getJsonConfig("miui_plus_connect_pc") 582 | XposedBridge.log("FM: Hooked com.xiaomi.mi_connect_service {o} @isPad, user's setting is $jsonResult") 583 | param.result = !try { 584 | jsonResult as Boolean 585 | } catch (e: Exception) { 586 | false 587 | } 588 | } 589 | } 590 | } catch (e: Exception) { 591 | XposedBridge.log(e) 592 | } 593 | }*/ 594 | 595 | //解锁通话 596 | /*"com.android.contacts" -> { 597 | XposedBridge.log("FM: Contacts app has found!") 598 | 599 | try { 600 | findMethod("com.android.contacts.activities.PeopleActivityV2") { 601 | name == "processIntent" && isNotStatic && returnType == Boolean::class.java && paramCount == 1 602 | }.hookMethod { 603 | before { 604 | XposedBridge.log("FM: Found method in [com.android.contacts.activities.PeopleActivityV2]") 605 | } 606 | after { param -> 607 | XposedBridge.log("FM: com.android.contacts.activities.PeopleActivityV2.processIntent() returns true") 608 | param.result = true 609 | } 610 | } 611 | } catch (e: Exception) { 612 | // XposedBridge.log(e) 613 | } 614 | 615 | try { 616 | findMethod("com.miui.contacts.common.SystemUtil") { 617 | name == "B" && isStatic && returnType == Boolean::class.java 618 | }.hookMethod { 619 | before { 620 | XposedBridge.log("FM: Found method in [com.miui.contacts.common.SystemUtil]") 621 | } 622 | after { param -> 623 | XposedBridge.log("FM: com.miui.contacts.common.SystemUtil.B() returns true") 624 | param.result = false 625 | } 626 | } 627 | } catch (e: Exception) { 628 | // XposedBridge.log(e) 629 | } 630 | 631 | try { 632 | findMethod("com.miui.contacts.common.SystemUtil") { 633 | name == "a" && isStatic && returnType == Boolean::class.java 634 | }.hookMethod { 635 | before { 636 | XposedBridge.log("FM: Found method in [com.miui.contacts.common.SystemUtil]") 637 | } 638 | after { param -> 639 | XposedBridge.log("FM: com.miui.contacts.common.SystemUtil.a() returns true") 640 | param.result = false 641 | } 642 | } 643 | } catch (e: Exception) { 644 | // XposedBridge.log(e) 645 | } 646 | } 647 | 648 | "com.android.server.telecom" -> { 649 | XposedBridge.log("FM: ContactServer app has found!") 650 | 651 | try { 652 | findMethod("com.android.server.telecom.components.UserCallIntentProcessor") { 653 | name == "isVoiceCapable" && isNotStatic && returnType == Boolean::class.java 654 | }.hookMethod { 655 | before { 656 | XposedBridge.log("FM: Found method in [com.android.server.telecom.components.UserCallIntentProcessor]") 657 | } 658 | after { param -> 659 | XposedBridge.log("FM: com.android.server.telecom.components.UserCallIntentProcessor.isVoiceCapable() returns true") 660 | param.result = true 661 | } 662 | } 663 | } catch (e: Exception) { 664 | XposedBridge.log(e) 665 | } 666 | }*/ 667 | 668 | } 669 | } 670 | } 671 | } --------------------------------------------------------------------------------