├── .gitignore
├── .idea
├── .gitignore
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── schemas
│ └── cn.ac.lz233.tarnhelm.logic.AppDatabase
│ │ ├── 3.json
│ │ └── 4.json
└── src
│ ├── androidTest
│ └── java
│ │ └── cn
│ │ └── ac
│ │ └── lz233
│ │ └── tarnhelm
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ │ └── cn
│ │ │ └── ac
│ │ │ └── lz233
│ │ │ └── tarnhelm
│ │ │ └── xposed
│ │ │ └── ModuleDataBridge.aidl
│ ├── assets
│ │ └── xposed_init
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── cn
│ │ │ └── ac
│ │ │ └── lz233
│ │ │ └── tarnhelm
│ │ │ ├── App.kt
│ │ │ ├── extension
│ │ │ ├── ExtensionClassLoader.kt
│ │ │ ├── ExtensionManager.kt
│ │ │ ├── ExtensionManagerService.kt
│ │ │ ├── ExtensionRecord.kt
│ │ │ ├── ExtensionRecordAdapter.kt
│ │ │ ├── MemoryDexLoader.java
│ │ │ ├── api
│ │ │ │ ├── ExtContext.java
│ │ │ │ ├── ExtService.java
│ │ │ │ ├── ExtSharedPreferences.java
│ │ │ │ ├── IExtConfigurationPanel.java
│ │ │ │ └── ITarnhelmExt.java
│ │ │ ├── exception
│ │ │ │ ├── ConfigurationPanelException.kt
│ │ │ │ └── InvalidExtensionException.kt
│ │ │ └── storage
│ │ │ │ ├── ExtensionOwnStorage.kt
│ │ │ │ └── ExtensionRecordStorage.kt
│ │ │ ├── logic
│ │ │ ├── AppDatabase.kt
│ │ │ ├── Network.kt
│ │ │ ├── dao
│ │ │ │ ├── ConfigDao.kt
│ │ │ │ ├── ExtensionDao.kt
│ │ │ │ ├── ParameterRuleDao.kt
│ │ │ │ ├── RedirectRuleDao.kt
│ │ │ │ ├── RegexRuleDao.kt
│ │ │ │ └── SettingsDao.kt
│ │ │ └── module
│ │ │ │ └── meta
│ │ │ │ ├── Extension.kt
│ │ │ │ ├── ParameterRule.kt
│ │ │ │ ├── RedirectRule.kt
│ │ │ │ └── RegexRule.kt
│ │ │ ├── receiver
│ │ │ └── BootBroadcast.kt
│ │ │ ├── service
│ │ │ ├── ClipboardService.kt
│ │ │ └── ModuleDataBridgeService.kt
│ │ │ ├── ui
│ │ │ ├── BaseActivity.kt
│ │ │ ├── SecondaryBaseActivity.kt
│ │ │ ├── extensions
│ │ │ │ └── ExtensionsActivity.kt
│ │ │ ├── main
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── PlaceHolderActivity.kt
│ │ │ ├── process
│ │ │ │ ├── ProcessCopyActivity.kt
│ │ │ │ ├── ProcessEditTextActivity.kt
│ │ │ │ ├── ProcessOverlayActivity.kt
│ │ │ │ ├── ProcessRulesActivity.kt
│ │ │ │ ├── ProcessServiceActivity.kt
│ │ │ │ ├── ProcessShareActivity.kt
│ │ │ │ └── ProcessShortcutActivity.kt
│ │ │ ├── rules
│ │ │ │ ├── DragSwipeCallback.kt
│ │ │ │ ├── IDragSwipe.kt
│ │ │ │ ├── RulesActivity.kt
│ │ │ │ ├── parameter
│ │ │ │ │ ├── ParameterRulesAdapter.kt
│ │ │ │ │ └── ParameterRulesFragment.kt
│ │ │ │ ├── redirect
│ │ │ │ │ ├── RedirectRulesAdapter.kt
│ │ │ │ │ └── RedirectRulesFragment.kt
│ │ │ │ └── regex
│ │ │ │ │ ├── RegexRulesAdapter.kt
│ │ │ │ │ └── RegexRulesFragment.kt
│ │ │ └── settings
│ │ │ │ ├── SettingsActivity.kt
│ │ │ │ ├── SettingsFragment.kt
│ │ │ │ └── backup
│ │ │ │ ├── BackupBottomSheetFragment.kt
│ │ │ │ └── SaveViaSAFActivity.kt
│ │ │ ├── util
│ │ │ ├── LogUtil.kt
│ │ │ └── ktx
│ │ │ │ ├── Extension.kt
│ │ │ │ ├── Float.kt
│ │ │ │ ├── IO.kt
│ │ │ │ ├── Int.kt
│ │ │ │ ├── Intent.kt
│ │ │ │ ├── JSONArray.kt
│ │ │ │ ├── JSONObject.kt
│ │ │ │ ├── List.kt
│ │ │ │ ├── OkHttp.kt
│ │ │ │ ├── Rule.kt
│ │ │ │ └── String.kt
│ │ │ ├── view
│ │ │ └── EditText.kt
│ │ │ └── xposed
│ │ │ ├── Config.kt
│ │ │ ├── XposedEntry.kt
│ │ │ ├── module
│ │ │ ├── Android.kt
│ │ │ ├── Self.kt
│ │ │ └── SystemUI.kt
│ │ │ └── util
│ │ │ ├── AMSHelper.kt
│ │ │ ├── KotlinXposedHelper.kt
│ │ │ └── ModuleBridgeHelper.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_add.xml
│ │ ├── ic_arrow_back.xml
│ │ ├── ic_check_circle.xml
│ │ ├── ic_copy.xml
│ │ ├── ic_delete.xml
│ │ ├── ic_error.xml
│ │ ├── ic_extension.xml
│ │ ├── ic_icon.xml
│ │ ├── ic_info.xml
│ │ ├── ic_launcher.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_open.xml
│ │ ├── ic_paste.xml
│ │ ├── ic_rule_folder.xml
│ │ ├── ic_save.xml
│ │ ├── ic_settings.xml
│ │ └── ic_share.xml
│ │ ├── layout
│ │ ├── activity_extensions.xml
│ │ ├── activity_main.xml
│ │ ├── activity_rules.xml
│ │ ├── activity_settings.xml
│ │ ├── dialog_about.xml
│ │ ├── dialog_parameter_rule_add.xml
│ │ ├── dialog_parameter_rule_edit.xml
│ │ ├── dialog_redirect_rule_add.xml
│ │ ├── dialog_redirect_rule_edit.xml
│ │ ├── dialog_regex_rule_add.xml
│ │ ├── dialog_regex_rule_edit.xml
│ │ ├── fragment_backup.xml
│ │ ├── fragment_parameter_rules.xml
│ │ ├── fragment_redirect_rules.xml
│ │ ├── fragment_regex_rules.xml
│ │ ├── item_extension.xml
│ │ ├── item_parameter_rule.xml
│ │ ├── item_redirect_rule.xml
│ │ ├── item_regex_rule.xml
│ │ ├── m3_icon_frame_open.xml
│ │ ├── m3_preference.xml
│ │ └── m3_preference_category.xml
│ │ ├── values-ja-rJP
│ │ └── strings.xml
│ │ ├── values-ja
│ │ └── strings.xml
│ │ ├── values-night-v32
│ │ └── themes.xml
│ │ ├── values-night
│ │ └── colors.xml
│ │ ├── values-v29
│ │ └── themes.xml
│ │ ├── values-v32
│ │ └── themes.xml
│ │ ├── values-yue-rCN
│ │ └── strings.xml
│ │ ├── values-zh-rCN
│ │ └── strings.xml
│ │ ├── values-zh-rHK
│ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ └── strings.xml
│ │ ├── values-zh
│ │ └── strings.xml
│ │ ├── values
│ │ ├── array.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── file_paths.xml
│ │ ├── locale_config.xml
│ │ ├── preferences.xml
│ │ └── shortcuts.xml
│ └── test
│ └── java
│ └── cn
│ └── ac
│ └── lz233
│ └── tarnhelm
│ └── ExampleUnitTest.kt
├── art
├── animation.pptx
├── animation.webp
├── banner.png
├── banner.psd
├── fdroid-badge.png
├── google-play-badge.png
├── icon-color-coolapk.png
├── icon-color.png
└── icon.svg
├── base
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── cn
│ │ └── ac
│ │ └── lz233
│ │ └── tarnhelm
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ └── AndroidManifest.xml
│ └── test
│ └── java
│ └── cn
│ └── ac
│ └── lz233
│ └── tarnhelm
│ └── ExampleUnitTest.kt
├── bin
├── test_cloud_backup.sh
└── test_d2d_backup.sh
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── full_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── 1.png
│ │ │ └── 2.png
│ └── short_description.txt
│ └── zh-CN
│ ├── full_description.txt
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── hidden-api
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── cn
│ │ └── ac
│ │ └── lz233
│ │ └── tarnhelm
│ │ └── hidden_api
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── android
│ │ └── app
│ │ └── AppOpsManagerHidden.java
│ └── test
│ └── java
│ └── cn
│ └── ac
│ └── lz233
│ └── tarnhelm
│ └── hidden_api
│ └── ExampleUnitTest.kt
├── license.md
├── settings.gradle
└── shizuku-service
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── cn
│ └── ac
│ └── lz233
│ └── tarnhelm
│ └── shizuku_service
│ └── ExampleInstrumentedTest.kt
├── main
├── AndroidManifest.xml
├── aidl
│ └── cn
│ │ └── ac
│ │ └── lz233
│ │ └── tarnhelm
│ │ └── service
│ │ ├── IClipboardShizukuService.aidl
│ │ └── ShizukuCallback.aidl
└── java
│ └── cn
│ └── ac
│ └── lz233
│ └── tarnhelm
│ └── service
│ └── ClipboardShizukuService.kt
└── test
└── java
└── cn
└── ac
└── lz233
└── tarnhelm
└── shizuku_service
└── ExampleUnitTest.kt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.aab
2 | *.apk
3 | *.iml
4 | .gradle
5 | .idea/
6 | .DS_Store
7 | build
8 | captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 | app/github
13 | app/fdroid
14 | app/google
15 | app/coolapk
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
43 | * It will hurt performance in most cases and will lead to incorrect execution of bytecode in the worst case.
44 | *
45 | * @param dexBytes dex file data
46 | * @param definingContext the class loader where the dex file will be attached to
47 | * @param name optional name for the dex file, may be null
48 | * @return a DexFile instance
49 | */
50 | @NonNull
51 | public static DexFile createDexFileFormBytes(@NonNull byte[] dexBytes, @NonNull ClassLoader definingContext, @Nullable String name) {
52 | Objects.requireNonNull(dexBytes, "dexBytes is null");
53 | Objects.requireNonNull(definingContext, "definingContext is null");
54 | if (dexBytes.length < 20) {
55 | throw new IllegalArgumentException("dexBytes is too short");
56 | }
57 | return createDexFileFormBytesAboveOreo(dexBytes, definingContext, name);
58 | }
59 |
60 | @RequiresApi(26)
61 | @NonNull
62 | private static DexFile createDexFileFormBytesAboveOreo(@NonNull byte[] dexBytes, @NonNull ClassLoader definingContext, @Nullable String name) {
63 | // Android 8.0 - 10: DexFile(ByteBuffer buf) throws IOException;
64 | // Android 10+: DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements);
65 | Constructor Implement this method and not
27 | * {@link #onOpNoted(String, int, String, String, int, int, int)} if callbacks are
28 | * required only on op notes for the default device {@link Context#DEVICE_ID_DEFAULT}.
29 | *
30 | * @param op The operation that was noted.
31 | * @param uid The UID performing the operation.
32 | * @param packageName The package performing the operation.
33 | * @param attributionTag The attribution tag performing the operation.
34 | * @param flags The flags of this op
35 | * @param result The result of the note.
36 | */
37 | void onOpNoted(String op, int uid, String packageName, String attributionTag, int flags, int result);
38 |
39 | /**
40 | * Similar to {@link #onOpNoted(String, int, String, String, int, int, int)},
41 | * but also includes the virtual device id of the op is now active or inactive.
42 | *
43 | * Implement this method if callbacks are required for op notes on all devices.
44 | * If not implemented explicitly, the default implementation will notify for the
45 | * default device {@link Context#DEVICE_ID_DEFAULT} only.
46 | *
47 | * If implemented, {@link #onOpNoted(String, int, String, String, int, int)}
48 | * will not be called automatically.
49 | *
50 | * @param op The operation that was noted.
51 | * @param uid The UID performing the operation.
52 | * @param packageName The package performing the operation.
53 | * @param attributionTag The attribution tag performing the operation.
54 | * @param virtualDeviceId the device that noted the operation
55 | * @param flags The flags of this op
56 | * @param result The result of the note.
57 | */
58 | default void onOpNoted(String op, int uid, String packageName, String attributionTag, int virtualDeviceId, int flags, int result) {
59 | if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
60 | onOpNoted(op, uid, packageName, attributionTag, flags, result);
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/hidden-api/src/test/java/cn/ac/lz233/tarnhelm/hidden_api/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.hidden_api
2 |
3 | import org.junit.Assert.assertEquals
4 | import org.junit.Test
5 |
6 | /**
7 | * Example local unit test, which will execute on the development machine (host).
8 | *
9 | * See [testing documentation](http://d.android.com/tools/testing).
10 | */
11 | class ExampleUnitTest {
12 | @Test
13 | fun addition_isCorrect() {
14 | assertEquals(4, 2 + 2)
15 | }
16 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven {
14 | url "https://api.xposed.info/"
15 | }
16 | }
17 | }
18 | rootProject.name = "Tarnhelm"
19 | include ':app'
20 | include ':hidden-api'
21 | include ':shizuku-service'
22 | include ':base'
23 |
--------------------------------------------------------------------------------
/shizuku-service/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/shizuku-service/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'dev.rikka.tools.refine' version '4.4.0'
5 | }
6 |
7 | android {
8 | namespace 'cn.ac.lz233.tarnhelm.shizuku_service'
9 | compileSdk 35
10 |
11 | defaultConfig {
12 | minSdk 27
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | }
17 |
18 | buildTypes {
19 | configureEach {
20 | buildConfigField 'String', 'APPLICATION_ID', '"cn.ac.lz233.tarnhelm"'
21 | }
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_17
29 | targetCompatibility JavaVersion.VERSION_17
30 | }
31 | kotlinOptions {
32 | jvmTarget = '17'
33 | }
34 | buildFeatures {
35 | buildConfig true
36 | aidl true
37 | }
38 | }
39 |
40 | dependencies {
41 | compileOnly project(":hidden-api")
42 |
43 | implementation "dev.rikka.tools.refine:runtime:4.4.0"
44 |
45 | implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
46 |
47 | implementation 'androidx.core:core-ktx:1.15.0'
48 | implementation 'androidx.appcompat:appcompat:1.7.0'
49 | implementation 'com.google.android.material:material:1.12.0'
50 | testImplementation 'junit:junit:4.13.2'
51 | androidTestImplementation 'androidx.test.ext:junit:1.2.1'
52 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
53 | }
--------------------------------------------------------------------------------
/shizuku-service/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lz233/Tarnhelm/4b314c2c7e4feaba1adc8736580a318f14d67652/shizuku-service/consumer-rules.pro
--------------------------------------------------------------------------------
/shizuku-service/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
--------------------------------------------------------------------------------
/shizuku-service/src/androidTest/java/cn/ac/lz233/tarnhelm/shizuku_service/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.shizuku_service
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("cn.ac.lz233.tarnhelm.shizuku_service.test", appContext.packageName)
21 | }
22 | }
--------------------------------------------------------------------------------
/shizuku-service/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 | > {
19 |
20 | override fun tag(): String = "ExtInfoMap"
21 |
22 | override fun decode(bytes: ByteArray, offset: Int, length: Int): List
>.toFlowString() = StringBuilder().apply {
30 | this@toFlowString.forEach { innerList ->
31 | append(" → ")
32 | innerList.forEach {
33 | append(it)
34 | append(" → ")
35 | }
36 | deleteRange(length - 3, length)
37 | append(" | ")
38 | append('\n')
39 | }
40 | deleteCharAt(lastIndex)
41 | }.toString()
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/util/ktx/OkHttp.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.util.ktx
2 |
3 | import cn.ac.lz233.tarnhelm.logic.Network
4 | import okhttp3.HttpUrl
5 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
6 | import okhttp3.Request
7 | import okhttp3.internal.commonToString
8 | import org.json.JSONObject
9 | import java.io.IOException
10 |
11 | fun HttpUrl.followRedirect(userAgent: String?): HttpUrl {
12 | val response = Network.okHttpClientNoRedirect
13 | .newCall(
14 | Request.Builder()
15 | .url(this)
16 | .let {
17 | if (userAgent.isNullOrEmpty()) it else it.addHeader("User-Agent", userAgent)
18 | }
19 | .build()
20 | )
21 | .execute()
22 | return if (response.isSuccessful) {
23 | // bilibili returns 404 but still using 200 header
24 | val body = response.body.string()
25 | runCatching {
26 | if (JSONObject(body).get("code").toString().contains("404")) throw IOException(body)
27 | }.onFailure {
28 | if (it is IOException) throw it
29 | }
30 | this
31 | } else if (response.isRedirect) {
32 | (response.header("Location")?.toHttpUrlOrNull() ?: response.header("location")?.toHttpUrlOrNull())
33 | ?.followRedirect(userAgent) ?: this
34 | } else {
35 | throw IOException(response.commonToString())
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/util/ktx/Rule.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.util.ktx
2 |
3 | import cn.ac.lz233.tarnhelm.logic.module.meta.ParameterRule
4 | import cn.ac.lz233.tarnhelm.logic.module.meta.RedirectRule
5 | import cn.ac.lz233.tarnhelm.logic.module.meta.RegexRule
6 | import org.json.JSONArray
7 | import org.json.JSONObject
8 |
9 | fun ParameterRule.toJSONObject() = JSONObject().apply {
10 | put("a", this@toJSONObject.description)
11 | put("e", this@toJSONObject.domain)
12 | put("f", this@toJSONObject.mode)
13 | put("g", JSONArray(this@toJSONObject.parametersArray))
14 | put("d", this@toJSONObject.author)
15 | }
16 |
17 | fun RegexRule.toJSONObject() = JSONObject().apply {
18 | put("a", this@toJSONObject.description)
19 | put("b", JSONArray(this@toJSONObject.regexArray))
20 | put("c", JSONArray(this@toJSONObject.replaceArray))
21 | put("d", this@toJSONObject.author)
22 | }
23 |
24 | fun RedirectRule.toJSONObject() = JSONObject().apply {
25 | put("a", this@toJSONObject.description)
26 | put("e", this@toJSONObject.domain)
27 | put("h", this@toJSONObject.userAgent)
28 | put("d", this@toJSONObject.author)
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/view/EditText.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.MotionEvent
6 | import com.google.android.material.textfield.TextInputEditText
7 |
8 | class EditText @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : TextInputEditText(context, attrs) {
9 | private var canTouch = true
10 |
11 | override fun onTouchEvent(event: MotionEvent?): Boolean {
12 | return if (canTouch) {
13 | super.onTouchEvent(event)
14 | } else {
15 | true
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/xposed/Config.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.xposed
2 |
3 | import cn.ac.lz233.tarnhelm.BuildConfig
4 | import de.robv.android.xposed.XSharedPreferences
5 |
6 | object Config {
7 | const val packageName = BuildConfig.APPLICATION_ID
8 | const val bridgeAction = "${BuildConfig.APPLICATION_ID}.bridge"
9 | lateinit var classLoader: ClassLoader
10 | val sp: XSharedPreferences
11 | get() = XSharedPreferences(BuildConfig.APPLICATION_ID, "${BuildConfig.APPLICATION_ID}_xposed")
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/xposed/XposedEntry.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.xposed
2 |
3 | import cn.ac.lz233.tarnhelm.xposed.module.Android
4 | import cn.ac.lz233.tarnhelm.xposed.module.Self
5 | import cn.ac.lz233.tarnhelm.xposed.module.SystemUI
6 | import de.robv.android.xposed.IXposedHookLoadPackage
7 | import de.robv.android.xposed.callbacks.XC_LoadPackage
8 |
9 | class XposedEntry : IXposedHookLoadPackage {
10 |
11 | override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
12 | Config.classLoader = lpparam.classLoader
13 | when (lpparam.packageName) {
14 | Config.packageName -> Self.init()
15 | "android" -> Android.init()
16 | "com.android.systemui" -> SystemUI.init()
17 | }
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/xposed/module/Self.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.xposed.module
2 |
3 | import cn.ac.lz233.tarnhelm.xposed.util.setReturnConstant
4 |
5 | object Self {
6 |
7 | fun init() {
8 | "cn.ac.lz233.tarnhelm.App\$Companion".setReturnConstant("isXposedActive", result = true)
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/xposed/module/SystemUI.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.xposed.module
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.PendingIntent
5 | import android.app.RemoteAction
6 | import android.content.ClipData
7 | import android.content.Context
8 | import android.content.Intent
9 | import android.graphics.drawable.Icon
10 | import android.view.View
11 | import android.widget.LinearLayout
12 | import androidx.core.view.get
13 | import androidx.core.view.size
14 | import cn.ac.lz233.tarnhelm.BuildConfig
15 | import cn.ac.lz233.tarnhelm.R
16 | import cn.ac.lz233.tarnhelm.ui.main.MainActivity
17 | import cn.ac.lz233.tarnhelm.ui.process.ProcessOverlayActivity
18 | import cn.ac.lz233.tarnhelm.util.LogUtil
19 | import cn.ac.lz233.tarnhelm.xposed.Config
20 | import cn.ac.lz233.tarnhelm.xposed.util.*
21 |
22 | object SystemUI {
23 | private var text1: CharSequence = ""
24 | private var text2: CharSequence = ""
25 |
26 | @SuppressLint("ResourceType")
27 | fun init() {
28 | runCatching {
29 | if ("com.android.systemui.clipboardoverlay.ClipboardOverlayView".findClassOrNull() == null) {
30 | // ????<=x<=TQ2A
31 | "com.android.systemui.clipboardoverlay.ClipboardOverlayController".hookAfterMethod(
32 | "setClipData",
33 | ClipData::class.java,
34 | String::class.java
35 | ) {
36 | if (!Config.sp.getBoolean("overrideClipboardOverlay", false)) return@hookAfterMethod
37 | LogUtil.xpd("setClipData ????<=x<=TQ2A")
38 |
39 | val clipboardOverlayController = it.thisObject
40 | val context = clipboardOverlayController.getObjectField("mContext") as Context
41 | //val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
42 | val actionContainer = clipboardOverlayController.getObjectField("mActionContainer") as LinearLayout
43 | // Android may call setClipData twice
44 | if ((actionContainer[actionContainer.size - 1] as View).contentDescription != "Tarnhelm") {
45 | val chip = it.thisObject.callMethod(
46 | "constructActionChip",
47 | RemoteAction(
48 | Icon.createWithResource(BuildConfig.APPLICATION_ID, R.drawable.ic_icon),
49 | "",
50 | "Tarnhelm",
51 | PendingIntent.getActivity(context, 1, Intent(context, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE)
52 | )
53 | ) as View
54 | chip.contentDescription = "Tarnhelm"
55 | chip.setOnClickListener {
56 | clipboardOverlayController.callMethod("animateOut")
57 | context.startActivity(Intent().setClassName(BuildConfig.APPLICATION_ID, ProcessOverlayActivity::class.java.name).apply {
58 | flags = Intent.FLAG_ACTIVITY_NEW_TASK
59 | })
60 | }
61 | actionContainer.addView(chip)
62 | }
63 | }
64 | } else {
65 | // >=TQ2A
66 | "com.android.systemui.clipboardoverlay.ClipboardOverlayView".hookAfterMethod(
67 | "resetActionChips"
68 | ) {
69 | if (!Config.sp.getBoolean("overrideClipboardOverlay", false)) return@hookAfterMethod
70 | LogUtil.xpd("setClipData >=TQ2A")
71 |
72 | val clipboardOverlayView = it.thisObject as View
73 | clipboardOverlayView.callMethod(
74 | "setActionChip",
75 | RemoteAction(
76 | Icon.createWithResource(BuildConfig.APPLICATION_ID, R.drawable.ic_icon),
77 | "",
78 | "Tarnhelm",
79 | PendingIntent.getActivity(
80 | clipboardOverlayView.context,
81 | 1,
82 | Intent().setClassName(BuildConfig.APPLICATION_ID, ProcessOverlayActivity::class.java.name),
83 | PendingIntent.FLAG_IMMUTABLE
84 | )
85 | ),
86 | object : Runnable {
87 | override fun run() {
88 | //clipboardOverlayController.callMethod("animateOut")
89 | }
90 | }
91 | )
92 | }
93 | }
94 | }.onFailure { LogUtil.xpe(it) }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/cn/ac/lz233/tarnhelm/xposed/util/AMSHelper.kt:
--------------------------------------------------------------------------------
1 | package cn.ac.lz233.tarnhelm.xposed.util
2 |
3 | import android.content.ComponentName
4 | import android.content.Context
5 | import android.content.pm.ApplicationInfo
6 | import android.content.pm.PackageManager
7 | import android.os.Build
8 | import cn.ac.lz233.tarnhelm.xposed.Config
9 |
10 | object AMSHelper {
11 |
12 | private fun getApplicationInfo(ams: Any): ApplicationInfo {
13 | val context = ams.getObjectFieldAs