3.0.0 Dev: Rewrite backend & Drop Magisk extension
This commit is contained in:
parent
43ddf597c7
commit
e38f1054f3
49 changed files with 865 additions and 1134 deletions
2
.github/workflows/PR_check.yml
vendored
2
.github/workflows/PR_check.yml
vendored
|
|
@ -46,4 +46,4 @@ jobs:
|
|||
id: buildDebug
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
echo "::set-output name=debugName::`ls app/build/outputs/apk/debug/V*-debug.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
echo "::set-output name=debugName::`ls app/build/apk/debug/HMA*-debug.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
|
|
|
|||
14
.github/workflows/main.yml
vendored
14
.github/workflows/main.yml
vendored
|
|
@ -57,27 +57,27 @@ jobs:
|
|||
id: buildRelease
|
||||
run: |
|
||||
./gradlew :app:assembleRelease
|
||||
echo "::set-output name=releaseName::`ls app/build/outputs/apk/release/V*-release.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
echo "::set-output name=releaseName::`ls app/build/apk/release/HMA*-release.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
|
||||
- name: Build debug
|
||||
id: buildDebug
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
echo "::set-output name=debugName::`ls app/build/outputs/apk/debug/V*-debug.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
echo "::set-output name=debugName::`ls app/build/apk/debug/HMA*-debug.apk | awk -F '(/|.apk)' '{print $6}'`"
|
||||
|
||||
- name: Upload release
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.buildRelease.outputs.releaseName }}
|
||||
path: "app/build/outputs/apk/release/*.apk"
|
||||
path: "app/build/apk/release/*.apk"
|
||||
|
||||
- name: Upload debug
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.buildDebug.outputs.debugName }}
|
||||
path: "app/build/outputs/apk/debug/*.apk"
|
||||
path: "app/build/apk/debug/*.apk"
|
||||
|
||||
- name: Upload mappings
|
||||
uses: actions/upload-artifact@v2
|
||||
|
|
@ -93,8 +93,8 @@ jobs:
|
|||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
run: |
|
||||
OUTPUT="app/build/outputs/apk/"
|
||||
export release=$(find $OUTPUT -name "V*-release.apk")
|
||||
export debug=$(find $OUTPUT -name "V*-debug.apk")
|
||||
OUTPUT="app/build/apk/"
|
||||
export release=$(find $OUTPUT -name "HMA*-release.apk")
|
||||
export debug=$(find $OUTPUT -name "HMA*-debug.apk")
|
||||
ESCAPED=`python3 -c 'import json,os,urllib.parse; msg = json.dumps(os.environ["COMMIT_MESSAGE"]); print(urllib.parse.quote(msg if len(msg) <= 1024 else json.dumps(os.environ["COMMIT_URL"])))'`
|
||||
curl -v "https://api.telegram.org/bot${BOT_TOKEN}/sendMediaGroup?chat_id=${CHANNEL_ID}&media=%5B%7B%22type%22:%22document%22,%20%22media%22:%22attach://release%22%7D,%7B%22type%22:%22document%22,%20%22media%22:%22attach://debug%22,%22caption%22:${ESCAPED}%7D%5D" -F release="@$release" -F debug="@$debug"
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -1,7 +1,5 @@
|
|||
# Hide My Applist
|
||||
|
||||
## This project is suspended 此项目已停更
|
||||
|
||||
[](https://github.com/Dr-TSNG)
|
||||
[](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest)
|
||||
[](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest)
|
||||
|
|
@ -27,18 +25,8 @@ This module can work as an Xposed module to hide apps or reject app list request
|
|||
该模块提供了一些检测方式用于测试您是否成功地隐藏了某些特定的包名,如 Magisk/Edxposed Manager;同时可作为 Xposed 模块用于隐藏应用列表或特定应用,保护隐私。
|
||||
|
||||
## Document
|
||||
### Maps scan rules
|
||||
Maps refers to /proc/self/maps (See [Linux manpage](https://man7.org/linux/man-pages/man5/proc.5.html) for more information).
|
||||
When something such as an Xposed module or a Zygisk module injects into target app, it will show its path on /proc/\<pid\>/maps. Though LSPosed and Riru did some work to make module maps info anonymous, if a module dlopen a native library by itself, the loaded so path will still be written on maps (Such module like QNotified).
|
||||
|
||||
How to use it: paths that contains configured strings will be filtered on /proc/self/maps
|
||||
Notice that under **MOST** circumstances you do not need to switch on this interception nor need to add any rule.
|
||||
### Custom query params
|
||||
This refers to the string params of methods of [PackageManagerService](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java)
|
||||
|
||||
How to use it: pms methods whose string params contain configured strings will be intercepted
|
||||
Notice that under **MOST** circumstances you do not need to switch on this interception nor need to add any rule.
|
||||
### How did HMA Magisk module work?
|
||||
HMA install inline hooks for syscalls and replace the path to dummy to make the app think there "isn't" suspicious files or directories.
|
||||
|
||||
However, syscall hook is very **unstable**, and can be detected by some methods. So do not switch on *file detection / maps scan* interceptions if not needed.
|
||||
|
|
|
|||
|
|
@ -1,152 +1,88 @@
|
|||
import com.android.build.api.component.analytics.AnalyticsEnabledApplicationVariant
|
||||
import com.android.build.api.variant.impl.ApplicationVariantImpl
|
||||
import com.android.build.gradle.BaseExtension
|
||||
import com.android.ide.common.signing.KeystoreHelper
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.PrintStream
|
||||
|
||||
val minSdkVer: Int by rootProject.extra
|
||||
val targetSdkVer: Int by rootProject.extra
|
||||
|
||||
val appVerName: String by rootProject.extra
|
||||
val appVerCode: Int by rootProject.extra
|
||||
val serviceVer: Int by rootProject.extra
|
||||
val minExtensionVer: Int by rootProject.extra
|
||||
val minBackupVer: Int by rootProject.extra
|
||||
|
||||
val gitCommitCount: String by rootProject.extra
|
||||
val gitCommitHash: String by rootProject.extra
|
||||
|
||||
val properties = Properties()
|
||||
properties.load(project.rootProject.file("local.properties").inputStream())
|
||||
import java.util.*
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.google.gms.google-services")
|
||||
id("dev.rikka.tools.refine")
|
||||
kotlin("android")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = targetSdkVer
|
||||
namespace = "com.tsng.hidemyapplist"
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.tsng.hidemyapplist"
|
||||
versionCode = appVerCode
|
||||
versionName = appVerName
|
||||
minSdk = minSdkVer
|
||||
targetSdk = targetSdkVer
|
||||
fun afterEval() = android.applicationVariants.forEach { variant ->
|
||||
val variantCapped = variant.name.capitalize(Locale.ROOT)
|
||||
val variantLowered = variant.name.toLowerCase(Locale.ROOT)
|
||||
|
||||
multiDexEnabled = false
|
||||
if (properties.getProperty("buildWithGitSuffix").toBoolean())
|
||||
versionNameSuffix = ".r${gitCommitCount}.${gitCommitHash}"
|
||||
|
||||
buildConfigField("int", "SERVICE_VERSION", serviceVer.toString())
|
||||
buildConfigField("int", "MIN_EXTENSION_VERSION", minExtensionVer.toString())
|
||||
buildConfigField("int", "MIN_BACKUP_VERSION", minBackupVer.toString())
|
||||
}
|
||||
|
||||
signingConfigs.create("config") {
|
||||
storeFile = file(properties.getProperty("fileDir"))
|
||||
storePassword = properties.getProperty("storePassword")
|
||||
keyAlias = properties.getProperty("keyAlias")
|
||||
keyPassword = properties.getProperty("keyPassword")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
signingConfigs.named("config").get().also {
|
||||
debug {
|
||||
signingConfig = it
|
||||
}
|
||||
release {
|
||||
signingConfig = it
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
|
||||
val outSrc = file("$outSrcDir/com/tsng/hidemyapplist/Magic.java")
|
||||
val signInfoTask = task("generate${variantCapped}SignInfo") {
|
||||
dependsOn("validateSigning${variantCapped}")
|
||||
outputs.file(outSrc)
|
||||
doLast {
|
||||
val sign = android.buildTypes[variantLowered].signingConfig
|
||||
outSrc.parentFile.mkdirs()
|
||||
val certificateInfo = KeystoreHelper.getCertificateInfo(
|
||||
sign?.storeType,
|
||||
sign?.storeFile,
|
||||
sign?.storePassword,
|
||||
sign?.keyPassword,
|
||||
sign?.keyAlias
|
||||
)
|
||||
PrintStream(outSrc).apply {
|
||||
println("package com.tsng.hidemyapplist;")
|
||||
println("public final class Magic {")
|
||||
print("public static final byte[] magicNumbers = {")
|
||||
val bytes = certificateInfo.certificate.encoded
|
||||
print(bytes.joinToString(",") { it.toString() })
|
||||
println("};")
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
variant.registerJavaGeneratingTask(signInfoTask, arrayListOf(outSrcDir))
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
val kotlinCompileTask = tasks.findByName("compile${variantCapped}Kotlin") as KotlinCompile
|
||||
kotlinCompileTask.dependsOn(signInfoTask)
|
||||
val srcSet = objects.sourceDirectorySet("magic", "magic").srcDir(outSrcDir)
|
||||
kotlinCompileTask.source(srcSet)
|
||||
|
||||
task<Sync>("build$variantCapped") {
|
||||
dependsOn("assemble$variantCapped")
|
||||
from("$buildDir/outputs/apk/$variantLowered")
|
||||
into("$buildDir/apk/$variantLowered")
|
||||
rename(".*.apk", "HMA-V${variant.versionName}-${variant.buildType.name}.apk")
|
||||
}
|
||||
}
|
||||
|
||||
// This code is forked from LSPosed
|
||||
// Make a class containing a byte array of signature
|
||||
androidComponents.onVariants { v ->
|
||||
val variant: ApplicationVariantImpl =
|
||||
if (v is ApplicationVariantImpl) v
|
||||
else (v as AnalyticsEnabledApplicationVariant).delegate as ApplicationVariantImpl
|
||||
val variantCapped = variant.name.capitalize()
|
||||
val variantLowered = variant.name.toLowerCase()
|
||||
|
||||
variant.outputs.forEach {
|
||||
it.outputFileName.set("V${it.versionName.get()}-${variant.buildType}.apk")
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
val app = rootProject.project(":app").extensions.getByName<BaseExtension>("android")
|
||||
val outSrcDir = file("$buildDir/generated/source/signInfo/${variantLowered}")
|
||||
val outSrc = file("$outSrcDir/com/tsng/hidemyapplist/Magic.java")
|
||||
val signInfoTask = tasks.register("generate${variantCapped}SignInfo") {
|
||||
dependsOn(":app:validateSigning${variantCapped}")
|
||||
outputs.file(outSrc)
|
||||
doLast {
|
||||
val sign = app.buildTypes.named(variantLowered).get().signingConfig
|
||||
outSrc.parentFile.mkdirs()
|
||||
val certificateInfo = KeystoreHelper.getCertificateInfo(
|
||||
sign?.storeType,
|
||||
sign?.storeFile,
|
||||
sign?.storePassword,
|
||||
sign?.keyPassword,
|
||||
sign?.keyAlias
|
||||
)
|
||||
PrintStream(outSrc).apply {
|
||||
println("package com.tsng.hidemyapplist;")
|
||||
println("public final class Magic {")
|
||||
print("public static final byte[] magicNumbers = {")
|
||||
val bytes = certificateInfo.certificate.encoded
|
||||
print(bytes.joinToString(",") { it.toString() })
|
||||
println("};")
|
||||
println("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
variant.variantData.registerJavaGeneratingTask(signInfoTask, arrayListOf(outSrcDir))
|
||||
|
||||
val kotlinCompileTask =
|
||||
tasks.findByName("compile${variant.name.capitalize()}Kotlin") as? SourceTask
|
||||
if (kotlinCompileTask != null) {
|
||||
kotlinCompileTask.dependsOn(signInfoTask)
|
||||
val srcSet = objects.sourceDirectorySet("magic", "magic").srcDir(outSrcDir)
|
||||
kotlinCompileTask.source(srcSet)
|
||||
}
|
||||
}
|
||||
afterEvaluate {
|
||||
afterEval()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.drakeet.about:about:2.5.0")
|
||||
implementation("com.drakeet.multitype:multitype:4.3.0")
|
||||
implementation("com.scwang.smart:refresh-layout-kernel:2.0.3")
|
||||
implementation("com.scwang.smart:refresh-header-material:2.0.3")
|
||||
implementation("com.github.kyuubiran:EzXHelper:0.6.1")
|
||||
implementation("com.github.topjohnwu.libsu:core:3.1.2")
|
||||
implementation(projects.common)
|
||||
runtimeOnly(projects.xposed)
|
||||
|
||||
implementation("com.google.code.gson:gson:2.8.9")
|
||||
implementation("com.google.android.material:material:1.5.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.3")
|
||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||
implementation("androidx.appcompat:appcompat:1.4.2")
|
||||
implementation("androidx.fragment:fragment-ktx:1.4.1")
|
||||
implementation("androidx.preference:preference-ktx:1.2.0")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.work:work-runtime-ktx:2.7.1")
|
||||
implementation("com.google.android.gms:play-services-ads:20.5.0")
|
||||
implementation("com.google.firebase:firebase-analytics-ktx:20.1.0")
|
||||
|
||||
compileOnly("de.robv.android.xposed:api:82")
|
||||
compileOnly("de.robv.android.xposed:api:82:sources")
|
||||
implementation("com.drakeet.about:about:2.5.1")
|
||||
implementation("com.drakeet.multitype:multitype:4.3.0")
|
||||
implementation("com.github.topjohnwu.libsu:core:3.1.2")
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
implementation("com.google.android.gms:play-services-ads:21.0.0")
|
||||
implementation("com.google.firebase:firebase-analytics-ktx:21.0.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.9.1")
|
||||
implementation("dev.rikka.hidden:compat:2.3.1")
|
||||
compileOnly("dev.rikka.hidden:stub:2.3.1")
|
||||
}
|
||||
|
|
|
|||
36
app/proguard-rules.pro
vendored
36
app/proguard-rules.pro
vendored
|
|
@ -1,24 +1,3 @@
|
|||
# 指定压缩级别
|
||||
-optimizationpasses 5
|
||||
# 混淆时采用的算法
|
||||
-optimizations !code/simplification/arithmetic, !field,!class/merging, !code/allocation/variable
|
||||
# 优化时允许访问并修改有修饰符的类和类的成员
|
||||
-allowaccessmodification
|
||||
# 将文件来源重命名为“SourceFile”字符串
|
||||
-renamesourcefileattribute SourceFile
|
||||
# 保持异常不被混淆
|
||||
-keepattributes Exceptions
|
||||
# 保留行号
|
||||
-keepattributes SourceFile, LineNumberTable
|
||||
# 保留注解不混淆
|
||||
-keepattributes *Annotation*,InnerClasses
|
||||
# 保持泛型
|
||||
-keepattributes Signature
|
||||
# 保持反射
|
||||
-keepattributes EnclosingMethod
|
||||
# 保留 native 方法的类名和方法名
|
||||
-keepclasseswithmembernames class * { native <methods>; }
|
||||
|
||||
# Magic
|
||||
-keep class com.tsng.hidemyapplist.Magic { *; }
|
||||
-keep class com.tsng.hidemyapplist.app.MyApplication {
|
||||
|
|
@ -30,13 +9,7 @@
|
|||
-keep class com.tsng.hidemyapplist.app.ui.activities.ModuleActivity$Fragment { *; }
|
||||
-keep class com.tsng.hidemyapplist.app.ui.views.FilterRulesView{ *; }
|
||||
|
||||
# Config
|
||||
-keep class com.tsng.hidemyapplist.JsonConfig { *; }
|
||||
-keep class com.tsng.hidemyapplist.JsonConfig$* { *; }
|
||||
|
||||
# Xposed
|
||||
-keep class com.tsng.hidemyapplist.xposed.XposedEntry
|
||||
-keep class com.tsng.hidemyapplist.xposed.PackageManagerService
|
||||
-keepclassmembers class com.tsng.hidemyapplist.app.MyApplication {
|
||||
static final boolean isModuleActivated;
|
||||
}
|
||||
|
|
@ -46,12 +19,3 @@
|
|||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# Dontwarn
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLParameters
|
||||
-dontwarn org.bouncycastle.jsse.BCSSLSocket
|
||||
-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||
-dontwarn org.conscrypt.Conscrypt*
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLParameters
|
||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||
Binary file not shown.
|
|
@ -1 +1 @@
|
|||
com.tsng.hidemyapplist.xposed.XposedEntry
|
||||
icu.nullptr.hidemyapplist.xposed.XposedEntry
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import androidx.annotation.StringRes
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.google.gson.Gson
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.MyApplication.Companion.appContext
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
fun makeToast(@StringRes resId: Int) {
|
||||
Toast.makeText(appContext, resId, Toast.LENGTH_SHORT).show()
|
||||
|
|
@ -17,7 +19,7 @@ fun makeToast(text: CharSequence) {
|
|||
Toast.makeText(appContext, text, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun <T : Any> T.deepCopy(): T = Gson().fromJson(Gson().toJson(this), this::class.java)
|
||||
inline fun <reified T> T.deepCopy(): T = Json.decodeFromString(Json.encodeToString(this))
|
||||
|
||||
fun AppCompatActivity.startFragment(fragment: Fragment, addToBackStack: Boolean = true) {
|
||||
val transaction = supportFragmentManager
|
||||
|
|
@ -36,4 +38,4 @@ fun Fragment.startFragment(fragment: Fragment, addToBackStack: Boolean = true) {
|
|||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
if (addToBackStack) transaction.addToBackStack(null)
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@ package com.tsng.hidemyapplist.app
|
|||
|
||||
|
||||
import com.tsng.hidemyapplist.BuildConfig
|
||||
import com.tsng.hidemyapplist.JsonConfig
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.MyApplication.Companion.appContext
|
||||
import icu.nullptr.hidemyapplist.common.JsonConfig
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
|
||||
|
||||
object JsonConfigManager {
|
||||
val configFile = File("${appContext.filesDir.absolutePath}/config.json")
|
||||
val globalConfig: JsonConfig
|
||||
|
||||
init {
|
||||
if (!configFile.exists())
|
||||
configFile.writeText(JsonConfig().toString())
|
||||
configFile.writeText(Json.encodeToString(JsonConfig()))
|
||||
try {
|
||||
globalConfig = JsonConfig.fromJson(configFile.readText())
|
||||
globalConfig = Json.decodeFromString(configFile.readText())
|
||||
val configVersion = globalConfig.configVersion
|
||||
if (configVersion < 49) throw RuntimeException("Config version too old")
|
||||
if (configVersion < 65) migrateFromPre65()
|
||||
|
|
@ -52,4 +54,4 @@ object JsonConfigManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,27 @@
|
|||
package com.tsng.hidemyapplist.app.helpers
|
||||
|
||||
import androidx.preference.PreferenceDataStore
|
||||
import com.github.kyuubiran.ezxhelper.utils.getFieldByClassOrObject
|
||||
import com.tsng.hidemyapplist.JsonConfig.AppConfig
|
||||
import com.tsng.hidemyapplist.JsonConfig.Template
|
||||
import icu.nullptr.hidemyapplist.common.JsonConfig
|
||||
|
||||
class TemplateDataStorage(private val template: Template) : PreferenceDataStore() {
|
||||
|
||||
}
|
||||
|
||||
class AppConfigDataStorage(private val appConfig: AppConfig) : PreferenceDataStore() {
|
||||
class AppConfigDataStorage(private val appConfig: JsonConfig.AppConfig) : PreferenceDataStore() {
|
||||
var isEnabled = false
|
||||
|
||||
override fun getBoolean(key: String, defValue: Boolean): Boolean {
|
||||
return if (key == "isEnabled") isEnabled
|
||||
else AppConfig::class.java.getFieldByClassOrObject(key).getBoolean(appConfig)
|
||||
else JsonConfig.AppConfig::class.java.getField(key).getBoolean(appConfig)
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String, value: Boolean) {
|
||||
if (key == "isEnabled") isEnabled = value
|
||||
else AppConfig::class.java.getFieldByClassOrObject(key).setBoolean(appConfig, value)
|
||||
else JsonConfig.AppConfig::class.java.getField(key).setBoolean(appConfig, value)
|
||||
}
|
||||
|
||||
override fun getStringSet(key: String, defValues: MutableSet<String>?): MutableSet<String> {
|
||||
return AppConfig::class.java.getFieldByClassOrObject(key)
|
||||
return JsonConfig.AppConfig::class.java.getField(key)
|
||||
.get(appConfig) as MutableSet<String>
|
||||
}
|
||||
|
||||
override fun putStringSet(key: String, values: MutableSet<String>?) {
|
||||
AppConfig::class.java.getFieldByClassOrObject(key).set(appConfig, values)
|
||||
JsonConfig.AppConfig::class.java.getField(key).set(appConfig, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,50 @@
|
|||
package com.tsng.hidemyapplist.app.helpers
|
||||
|
||||
import com.tsng.hidemyapplist.app.MyApplication.Companion.appContext
|
||||
import android.os.Parcel
|
||||
import android.os.RemoteException
|
||||
import android.os.ServiceManager
|
||||
import android.util.Log
|
||||
import icu.nullptr.hidemyapplist.common.Constants
|
||||
import icu.nullptr.hidemyapplist.common.IHMAService
|
||||
|
||||
object ServiceHelper {
|
||||
@JvmStatic
|
||||
fun getServiceVersion(): Int {
|
||||
return try {
|
||||
appContext.packageManager.getInstallerPackageName("getServiceVersion")!!.toInt()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getExtensionVersion(): Int {
|
||||
return try {
|
||||
appContext.packageManager.getInstallerPackageName("getExtensionVersion")!!.toInt()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
private const val TAG = "ServiceHelper"
|
||||
|
||||
@JvmStatic
|
||||
fun getServeTimes(): Int {
|
||||
private fun getService(): IHMAService? {
|
||||
val pm = ServiceManager.getService("package")
|
||||
val data = Parcel.obtain()
|
||||
val reply = Parcel.obtain()
|
||||
return try {
|
||||
appContext.packageManager.getInstallerPackageName("getServeTimes")!!.toInt()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLogs(): String? {
|
||||
return try {
|
||||
appContext.packageManager.getInstallerPackageName("getLogs")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
data.enforceInterface(Constants.DESCRIPTOR)
|
||||
data.writeInt(Constants.ACTION_GET_BINDER)
|
||||
pm.transact(Constants.TRANSACTION, data, reply, 0)
|
||||
val binder = reply.readStrongBinder()
|
||||
IHMAService.Stub.asInterface(binder)
|
||||
} catch (e: RemoteException) {
|
||||
Log.d(TAG, "Failed to get binder")
|
||||
null
|
||||
} finally {
|
||||
data.recycle()
|
||||
reply.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getServiceVersion() = getService()?.serviceVersion ?: 0
|
||||
|
||||
fun getServeTimes() = getService()?.filterCount ?: 0
|
||||
|
||||
fun getLogs() = getService()?.logs
|
||||
|
||||
fun cleanLogs() {
|
||||
try {
|
||||
appContext.packageManager.getInstallerPackageName("cleanLogs")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
}
|
||||
getService()?.clearLogs()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun submitConfig(json: String) {
|
||||
try {
|
||||
appContext.packageManager.getInstallerPackageName("submitConfig#$json")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
}
|
||||
getService()?.syncConfig(json)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun stopSystemService(cleanEnv: Boolean) {
|
||||
try {
|
||||
appContext.packageManager.getInstallerPackageName("stopSystemService#$cleanEnv")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
}
|
||||
getService()?.stopService(cleanEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ class AboutActivity : AbsAboutActivity() {
|
|||
items.add(Card(getString(R.string.about_how_to_use_description_1)))
|
||||
items.add(Line())
|
||||
items.add(Card(getString(R.string.about_how_to_use_description_2)))
|
||||
items.add(Line())
|
||||
items.add(Card(getString(R.string.about_how_to_use_description_3)))
|
||||
|
||||
items.add(Category(getString(R.string.about_hook_differences_title)))
|
||||
items.add(Card(getString(R.string.about_hook_differences_description)))
|
||||
|
|
@ -52,8 +50,6 @@ class AboutActivity : AbsAboutActivity() {
|
|||
items.add(License("SmartRefreshLayout", "scwang90", License.APACHE_2, "https://github.com/scwang90/SmartRefreshLayout"))
|
||||
items.add(License("EzXHelper", "KyuubiRan", License.APACHE_2, "https://github.com/KyuubiRan/EzXHelper"))
|
||||
items.add(License("libsu", "topjohnwu", License.APACHE_2, "https://github.com/topjohnwu/libsu"))
|
||||
items.add(License("Gson", "Google", License.APACHE_2, "https://github.com/google/gson"))
|
||||
items.add(License("okhttp", "square", License.APACHE_2, "https://github.com/square/okhttp"))
|
||||
items.add(License("linux-syscall-support", "Google", "Google", "https://chromium.googlesource.com/linux-syscall-support"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tsng.hidemyapplist.app.ui.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
|
|
@ -23,12 +22,10 @@ import com.tsng.hidemyapplist.app.MyApplication.Companion.appContext
|
|||
import com.tsng.hidemyapplist.app.SubmitConfigService
|
||||
import com.tsng.hidemyapplist.app.helpers.ServiceHelper
|
||||
import com.tsng.hidemyapplist.app.makeToast
|
||||
import com.tsng.hidemyapplist.app.ui.views.ShellDialog
|
||||
import com.tsng.hidemyapplist.databinding.ActivityMainBinding
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
|
@ -53,16 +50,12 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
||||
binding.menuDetectionTest.setOnClickListener(this)
|
||||
binding.menuInstallExtension.setOnClickListener(this)
|
||||
binding.menuTemplateManage.setOnClickListener(this)
|
||||
binding.menuScopeManage.setOnClickListener(this)
|
||||
binding.menuLogs.setOnClickListener(this)
|
||||
binding.menuSettings.setOnClickListener(this)
|
||||
binding.menuAbout.setOnClickListener(this)
|
||||
|
||||
if (MyApplication.isModuleActivated)
|
||||
binding.menuInstallExtension.visibility = View.VISIBLE
|
||||
|
||||
setContentView(binding.root)
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
MobileAds.initialize(appContext)
|
||||
|
|
@ -92,7 +85,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
if (serviceVersion != 0) {
|
||||
binding.serviceStatusText.text =
|
||||
if (serviceVersion != BuildConfig.SERVICE_VERSION)
|
||||
if (serviceVersion != icu.nullptr.hidemyapplist.common.BuildConfig.SERVICE_VERSION)
|
||||
getString(R.string.xposed_service_old)
|
||||
else
|
||||
getString(R.string.xposed_service_on) + " [$serviceVersion]"
|
||||
|
|
@ -100,13 +93,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
binding.serveTimes.visibility = View.VISIBLE
|
||||
binding.serveTimes.text = text[0] + ServiceHelper.getServeTimes() + text[2]
|
||||
binding.extensionStatusText.visibility = View.VISIBLE
|
||||
binding.extensionStatusText.text = when (val extensionVersion = ServiceHelper.getExtensionVersion()) {
|
||||
0 -> getString(R.string.extension_not_installed)
|
||||
-1 -> getString(R.string.extension_version_too_old)
|
||||
-2 -> getString(R.string.extension_apk_version_too_old)
|
||||
else -> getString(R.string.extension_installed) + " [$extensionVersion]"
|
||||
}
|
||||
} else {
|
||||
binding.serveTimes.visibility = View.GONE
|
||||
binding.serviceStatusText.text = getString(R.string.xposed_service_off)
|
||||
|
|
@ -115,29 +101,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.menu_install_extension -> {
|
||||
val listener: (DialogInterface, Int) -> Unit = { dialog, which ->
|
||||
val flavor = if (which == DialogInterface.BUTTON_POSITIVE) "Zygisk" else "Riru"
|
||||
val zipFile = File("$cacheDir/$flavor-HideMyApplist.zip")
|
||||
assets.open("extension.zip").use { fis ->
|
||||
zipFile.outputStream().use {
|
||||
fis.copyTo(it)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
ShellDialog(this)
|
||||
.setCommands("su --mount-master -c magisk --install-module ${zipFile.absolutePath}")
|
||||
.create()
|
||||
}
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.install_magisk_extension_title)
|
||||
.setMessage(R.string.install_magisk_extension_message)
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.setNegativeButton("Riru", listener)
|
||||
.setPositiveButton("Zygisk", listener)
|
||||
.show()
|
||||
}
|
||||
R.id.menu_detection_test -> {
|
||||
val intent = packageManager.getLaunchIntentForPackage("icu.nullptr.applistdetector")
|
||||
if (intent == null) {
|
||||
|
|
|
|||
|
|
@ -6,13 +6,16 @@ import androidx.appcompat.widget.SearchView
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kyuubiran.ezxhelper.utils.runOnMainThread
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.helpers.AppInfoHelper
|
||||
import com.tsng.hidemyapplist.app.ui.adapters.AppSelectAdapter
|
||||
import com.tsng.hidemyapplist.app.ui.views.Ads
|
||||
import com.tsng.hidemyapplist.databinding.FragmentAppSelectBinding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
|
@ -51,7 +54,11 @@ class AppSelectFragment : Fragment() {
|
|||
): View {
|
||||
binding = FragmentAppSelectBinding.inflate(inflater, container, false)
|
||||
binding.adBanner.loadAd(Ads.appSelectAd)
|
||||
binding.refreshLayout.setOnRefreshListener { refresh() }.autoRefresh()
|
||||
binding.refreshLayout.setOnRefreshListener {
|
||||
lifecycleScope.launch { refresh() }
|
||||
}
|
||||
binding.refreshLayout.isRefreshing = true
|
||||
lifecycleScope.launch { refresh() }
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
|
@ -89,14 +96,14 @@ class AppSelectFragment : Fragment() {
|
|||
return true
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
thread {
|
||||
initAppListView()
|
||||
runOnMainThread { binding.refreshLayout.finishRefresh() }
|
||||
private suspend fun refresh() {
|
||||
initAppListView()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.refreshLayout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAppListView() {
|
||||
private suspend fun initAppListView() {
|
||||
val appInfoList = AppInfoHelper.getAppInfoList()
|
||||
appInfoList.sortWith { o1, o2 ->
|
||||
val c1 = selectedApps.contains(o1.packageName)
|
||||
|
|
@ -104,10 +111,10 @@ class AppSelectFragment : Fragment() {
|
|||
if (c1 != c2) return@sortWith if (c1) -1 else 1
|
||||
Collator.getInstance(Locale.getDefault()).compare(o1.appName, o2.appName)
|
||||
}
|
||||
runOnMainThread {
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.appSelect.layoutManager = LinearLayoutManager(activity)
|
||||
adapter = AppSelectAdapter(isShowSystemApp, true, appInfoList, selectedApps)
|
||||
binding.appSelect.adapter = adapter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.preference.Preference
|
|||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tsng.hidemyapplist.JsonConfig
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager.globalConfig
|
||||
|
|
@ -22,6 +21,7 @@ import com.tsng.hidemyapplist.app.helpers.AppConfigDataStorage
|
|||
import com.tsng.hidemyapplist.app.makeToast
|
||||
import com.tsng.hidemyapplist.app.startFragment
|
||||
import com.tsng.hidemyapplist.app.ui.views.FilterRulesView
|
||||
import icu.nullptr.hidemyapplist.common.JsonConfig
|
||||
|
||||
class AppSettingsFragment : PreferenceFragmentCompat() {
|
||||
companion object {
|
||||
|
|
@ -183,4 +183,4 @@ class AppSettingsFragment : PreferenceFragmentCompat() {
|
|||
else scope.remove(packageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import android.os.Bundle
|
|||
import android.view.*
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.kyuubiran.ezxhelper.utils.runOnMainThread
|
||||
import com.tsng.hidemyapplist.BuildConfig
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager.globalConfig
|
||||
|
|
@ -14,9 +14,11 @@ import com.tsng.hidemyapplist.app.startFragment
|
|||
import com.tsng.hidemyapplist.app.ui.adapters.AppSelectAdapter
|
||||
import com.tsng.hidemyapplist.app.ui.views.Ads
|
||||
import com.tsng.hidemyapplist.databinding.FragmentAppSelectBinding
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class ScopeManageFragment : Fragment() {
|
||||
|
||||
|
|
@ -37,7 +39,11 @@ class ScopeManageFragment : Fragment() {
|
|||
): View {
|
||||
binding = FragmentAppSelectBinding.inflate(inflater, container, false)
|
||||
binding.adBanner.loadAd(Ads.appSelectAd)
|
||||
binding.refreshLayout.setOnRefreshListener { refresh() }.autoRefresh()
|
||||
binding.refreshLayout.setOnRefreshListener {
|
||||
lifecycleScope.launch { refresh() }
|
||||
}
|
||||
binding.refreshLayout.isRefreshing = true
|
||||
lifecycleScope.launch { refresh() }
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
|
@ -72,14 +78,14 @@ class ScopeManageFragment : Fragment() {
|
|||
return true
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
thread {
|
||||
private suspend fun refresh() {
|
||||
initAppListView()
|
||||
runOnMainThread { binding.refreshLayout.finishRefresh() }
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.refreshLayout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAppListView() {
|
||||
private suspend fun initAppListView() {
|
||||
val appInfoList = AppInfoHelper.getAppInfoList()
|
||||
appInfoList.removeIf { it.packageName == BuildConfig.APPLICATION_ID }
|
||||
val selectedApps = globalConfig.scope.keys
|
||||
|
|
@ -89,7 +95,7 @@ class ScopeManageFragment : Fragment() {
|
|||
if (b1 != b2) return@sortWith if (b1) -1 else 1
|
||||
Collator.getInstance(Locale.getDefault()).compare(o1.appName, o2.appName)
|
||||
}
|
||||
runOnMainThread {
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.appSelect.layoutManager = LinearLayoutManager(activity)
|
||||
adapter = AppSelectAdapter(isShowSystemApp, false, appInfoList, selectedApps) {
|
||||
itemView.setOnClickListener {
|
||||
|
|
@ -101,4 +107,4 @@ class ScopeManageFragment : Fragment() {
|
|||
binding.appSelect.adapter = adapter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,19 +9,19 @@ import androidx.preference.Preference
|
|||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.tsng.hidemyapplist.BuildConfig
|
||||
import com.tsng.hidemyapplist.JsonConfig
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager
|
||||
import com.tsng.hidemyapplist.app.MyApplication.Companion.appContext
|
||||
import com.tsng.hidemyapplist.app.helpers.ServiceHelper
|
||||
import com.tsng.hidemyapplist.app.makeToast
|
||||
import icu.nullptr.hidemyapplist.common.BuildConfig
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val backupImportSAFLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
|
|
@ -32,23 +32,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
val backupJson: JsonObject
|
||||
val backupVersion: Int
|
||||
try {
|
||||
backupJson = JsonParser.parseString(backup).asJsonObject
|
||||
backupVersion = backupJson["configVersion"].asInt
|
||||
|
||||
backupJson = Json.parseToJsonElement(backup) as JsonObject
|
||||
backupVersion = backupJson["configVersion"].toString().toInt()
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(getString(R.string.settings_import_file_damaged))
|
||||
.apply { addSuppressed(e) }
|
||||
}
|
||||
if (backupVersion > BuildConfig.VERSION_CODE)
|
||||
if (backupVersion > BuildConfig.SERVICE_VERSION)
|
||||
throw RuntimeException(getString(R.string.settings_import_app_version_too_old))
|
||||
if (backupVersion < BuildConfig.MIN_BACKUP_VERSION)
|
||||
throw RuntimeException(getString(R.string.settings_import_backup_version_too_old))
|
||||
JsonConfigManager.edit {
|
||||
templates.clear()
|
||||
for ((name, template) in backupJson["templates"].asJsonObject.entrySet())
|
||||
templates[name] = Gson().fromJson(template.toString(), JsonConfig.Template::class.java)
|
||||
for ((name, template) in backupJson["templates"] as JsonObject)
|
||||
templates[name] = Json.decodeFromString(template.toString())
|
||||
scope.clear()
|
||||
for ((name, appConfig) in backupJson["scope"].asJsonObject.entrySet())
|
||||
scope[name] = Gson().fromJson(appConfig.toString(), JsonConfig.AppConfig::class.java)
|
||||
for ((name, appConfig) in backupJson["scope"] as JsonObject)
|
||||
scope[name] = Json.decodeFromString(appConfig.toString())
|
||||
}
|
||||
makeToast(R.string.settings_import_successful)
|
||||
} catch (e: Exception) {
|
||||
|
|
@ -171,4 +172,4 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import android.view.*
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.setFragmentResultListener
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.tsng.hidemyapplist.JsonConfig
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager
|
||||
import com.tsng.hidemyapplist.app.JsonConfigManager.globalConfig
|
||||
|
|
@ -15,6 +14,7 @@ import com.tsng.hidemyapplist.app.makeToast
|
|||
import com.tsng.hidemyapplist.app.startFragment
|
||||
import com.tsng.hidemyapplist.app.ui.views.FilterRulesView
|
||||
import com.tsng.hidemyapplist.databinding.FragmentTemplateSettingsBinding
|
||||
import icu.nullptr.hidemyapplist.common.JsonConfig
|
||||
|
||||
class TemplateSettingsFragment : Fragment() {
|
||||
companion object {
|
||||
|
|
@ -156,4 +156,4 @@ class TemplateSettingsFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
package com.tsng.hidemyapplist.app.ui.views
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.topjohnwu.superuser.CallbackList
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.tsng.hidemyapplist.R
|
||||
import com.tsng.hidemyapplist.databinding.ShellDialogBinding
|
||||
|
||||
|
||||
class ShellDialog(private val context: Context) {
|
||||
|
||||
private val binding = ShellDialogBinding.inflate(LayoutInflater.from(context))
|
||||
private val console = object : CallbackList<String?>() {
|
||||
override fun onAddElement(s: String?) {
|
||||
binding.console.append("$s\n")
|
||||
}
|
||||
}
|
||||
|
||||
private var commands: Array<out String>? = null
|
||||
private var successfulMainButtonText = context.getText(android.R.string.ok)
|
||||
private var successfulViceButtonText: CharSequence? = null
|
||||
private var failedButtonText = context.getText(android.R.string.ok)
|
||||
private var successfulMainButtonClickListener: View.OnClickListener? = null
|
||||
private var successfulViceButtonClickListener: View.OnClickListener? = null
|
||||
private var failedButtonClickListener: View.OnClickListener? = null
|
||||
|
||||
fun setCommands(vararg cmd: String): ShellDialog {
|
||||
commands = cmd
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSuccessfulMainButton(@StringRes ResId: Int?, onClickListener: View.OnClickListener?): ShellDialog {
|
||||
ResId?.let { successfulMainButtonText = context.getText(it) }
|
||||
successfulMainButtonClickListener = onClickListener
|
||||
return this
|
||||
}
|
||||
|
||||
fun setSuccessfulViceButton(@StringRes ResId: Int?, onClickListener: View.OnClickListener?): ShellDialog {
|
||||
successfulViceButtonText = context.getText(ResId ?: android.R.string.cancel)
|
||||
successfulViceButtonClickListener = onClickListener
|
||||
return this
|
||||
}
|
||||
|
||||
fun setFailedButton(@StringRes ResId: Int?, onClickListener: View.OnClickListener?): ShellDialog {
|
||||
ResId?.let { failedButtonText = context.getText(it) }
|
||||
failedButtonClickListener = onClickListener
|
||||
return this
|
||||
}
|
||||
|
||||
fun create() {
|
||||
commands ?: throw IllegalArgumentException("Commands not set")
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.shell)
|
||||
.setView(binding.root)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Stub!", null)
|
||||
.setNegativeButton("Stub!", null)
|
||||
.create()
|
||||
.apply {
|
||||
create()
|
||||
getButton(Dialog.BUTTON_POSITIVE).visibility = View.GONE
|
||||
getButton(Dialog.BUTTON_NEGATIVE).visibility = View.GONE
|
||||
show()
|
||||
}
|
||||
|
||||
if (!Shell.getShell().isRoot) {
|
||||
console.add(context.getString(R.string.no_root_permission))
|
||||
dialog.getButton(Dialog.BUTTON_POSITIVE).apply {
|
||||
visibility = View.VISIBLE
|
||||
text = failedButtonText
|
||||
setOnClickListener { failedButtonClickListener; dialog.dismiss() }
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Shell.su(*commands!!).to(console, console).submit { out: Shell.Result ->
|
||||
console.add("------------")
|
||||
console.add("result code: ${out.code}")
|
||||
if (out.isSuccess) {
|
||||
console.add(context.getString(R.string.execute_successfully))
|
||||
dialog.getButton(Dialog.BUTTON_POSITIVE).apply {
|
||||
visibility = View.VISIBLE
|
||||
text = successfulMainButtonText
|
||||
setOnClickListener { successfulMainButtonClickListener?.onClick(it); dialog.dismiss() }
|
||||
}
|
||||
if (successfulViceButtonText != null)
|
||||
dialog.getButton(Dialog.BUTTON_NEGATIVE).apply {
|
||||
visibility = View.VISIBLE
|
||||
text = successfulViceButtonText
|
||||
setOnClickListener { successfulViceButtonClickListener?.onClick(it); dialog.dismiss() }
|
||||
}
|
||||
} else {
|
||||
console.add(context.getString(R.string.execute_failed))
|
||||
dialog.getButton(Dialog.BUTTON_POSITIVE).apply {
|
||||
visibility = View.VISIBLE
|
||||
text = failedButtonText
|
||||
setOnClickListener { failedButtonClickListener?.onClick(it); dialog.dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,493 +0,0 @@
|
|||
/*
|
||||
* This file is part of Hide My Applist.
|
||||
*
|
||||
* Hide My Applist is free software: you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Affero General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
* License along with Hide My Applist.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 Hide My Applist Contributors
|
||||
*/
|
||||
|
||||
package com.tsng.hidemyapplist.xposed
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import com.github.kyuubiran.ezxhelper.utils.*
|
||||
import com.tsng.hidemyapplist.BuildConfig
|
||||
import com.tsng.hidemyapplist.JsonConfig
|
||||
import com.tsng.hidemyapplist.xposed.ServiceUtils.getBinderCaller
|
||||
import com.tsng.hidemyapplist.xposed.ServiceUtils.getRecursiveField
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.reflect.Method
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
object PackageManagerService {
|
||||
private const val hmaApp = "com.tsng.hidemyapplist"
|
||||
|
||||
private val customPms = arrayOf(
|
||||
"com.android.server.pm.OplusPackageManagerService",
|
||||
"com.android.server.pm.OppoPackageManagerService"
|
||||
)
|
||||
|
||||
private val allHooks = mutableSetOf<XC_MethodHook.Unhook>()
|
||||
private val systemApps = mutableSetOf<String>()
|
||||
private var stopped = false
|
||||
private var configCached = false
|
||||
private var extensionVersion = 0
|
||||
private var mLock = Any()
|
||||
|
||||
private lateinit var dataDir: String
|
||||
private lateinit var logFile: File
|
||||
private lateinit var token: String
|
||||
|
||||
@Volatile
|
||||
private var config = JsonConfig()
|
||||
|
||||
@Volatile
|
||||
private var configStr = JsonConfig().toString()
|
||||
|
||||
@Volatile
|
||||
private var interceptionCount = 0
|
||||
|
||||
private object Log {
|
||||
fun d(msg: String) {
|
||||
if (!config.detailLog) return
|
||||
val s = "[HMA Xposed] [DEBUG] $msg"
|
||||
XposedBridge.log(s)
|
||||
addLog(s)
|
||||
}
|
||||
|
||||
fun i(msg: String) {
|
||||
val s = "[HMA Xposed] [INFO] $msg"
|
||||
XposedBridge.log(s)
|
||||
addLog(s)
|
||||
}
|
||||
|
||||
fun e(msg: String) {
|
||||
val s = "[HMA Xposed] [ERROR] $msg"
|
||||
XposedBridge.log(s)
|
||||
addLog(s)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRandomString(length: Int): String {
|
||||
val leftLimit = 97 // letter 'a'
|
||||
val rightLimit = 122 // letter 'z'
|
||||
val random = Random()
|
||||
val buffer = StringBuilder(length)
|
||||
for (i in 0 until length) {
|
||||
val randomLimitedInt = leftLimit + (random.nextFloat() * (rightLimit - leftLimit + 1)).toInt()
|
||||
buffer.append(randomLimitedInt.toChar())
|
||||
}
|
||||
return buffer.toString()
|
||||
}
|
||||
|
||||
private fun generateToken() {
|
||||
token = generateRandomString(10)
|
||||
File("$dataDir/tmp/token").writeText(token)
|
||||
}
|
||||
|
||||
private fun addLog(log: String) {
|
||||
synchronized(mLock) {
|
||||
if (logFile.length() / 1024 > config.maxLogSize) logFile.delete()
|
||||
val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss ", Locale.getDefault()).format(Date())
|
||||
logFile.appendText(date + log + '\n')
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideLogs(): String {
|
||||
try {
|
||||
synchronized(mLock) {
|
||||
if (stopped) throw InterruptedException("Service stopped")
|
||||
val sb = StringBuilder()
|
||||
val list = logFile.readLines()
|
||||
for (line in list) sb.append(line).append("\n")
|
||||
return sb.toString()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return "Failed to load logs\n" + e.stackTraceToString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initConfig() {
|
||||
configStr = File("$dataDir/config.json").readText()
|
||||
config = JsonConfig.fromJson(configStr)
|
||||
if (config.configVersion < BuildConfig.SERVICE_VERSION) {
|
||||
config = JsonConfig()
|
||||
Log.i("Config cache version too old, need refresh")
|
||||
return
|
||||
}
|
||||
Log.d("Cached config: $config")
|
||||
configCached = true
|
||||
try {
|
||||
interceptionCount = File("$dataDir/interception_cnt").readText().toInt()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
Log.i("Config initialized")
|
||||
}
|
||||
|
||||
private fun updateConfig(json: String) {
|
||||
if (configStr == json) return
|
||||
configStr = json
|
||||
config = JsonConfig.fromJson(json)
|
||||
synchronized(mLock) {
|
||||
if (stopped) return
|
||||
File("$dataDir/config.json").writeText(json)
|
||||
}
|
||||
if (!configCached) initConfig()
|
||||
else Log.d("Update config: $config")
|
||||
}
|
||||
|
||||
private fun isUseHook(caller: String?, hookMethod: String): Boolean {
|
||||
val appConfig = config.scope[caller] ?: return false
|
||||
return appConfig.enableAllHooks || appConfig.applyHooks.contains(hookMethod)
|
||||
}
|
||||
|
||||
private fun isToHide(caller: String?, query: String?): Boolean {
|
||||
if (caller == null || query == null) return false
|
||||
if (caller in query) return false
|
||||
val appConfig = config.scope[caller] ?: return false
|
||||
if (appConfig.useWhitelist && appConfig.excludeSystemApps && query in systemApps) return false
|
||||
|
||||
if (query in appConfig.extraAppList || query in appConfig.extraQueryParamRules)
|
||||
return !appConfig.useWhitelist
|
||||
for (tplName in appConfig.applyTemplates) {
|
||||
val tpl = config.templates[tplName]!!
|
||||
if (query in tpl.appList || query in tpl.queryParamRules)
|
||||
return !appConfig.useWhitelist
|
||||
}
|
||||
|
||||
return appConfig.useWhitelist
|
||||
}
|
||||
|
||||
private fun removeList(
|
||||
method: Method,
|
||||
isParceled: Boolean,
|
||||
hookMethod: String,
|
||||
pkgNameObjList: List<String>
|
||||
) {
|
||||
allHooks.add(method.hookAfter { param ->
|
||||
if (param.hasThrowable()) return@hookAfter
|
||||
val caller = param.thisObject.getBinderCaller()
|
||||
if (!isUseHook(caller, hookMethod)) return@hookAfter
|
||||
|
||||
var isHidden = false
|
||||
val list =
|
||||
if (isParceled) param.result.invokeMethodAs<MutableList<Any>>("getList")!!
|
||||
else param.result as MutableList<Any>
|
||||
val iterator = list.iterator()
|
||||
val removed = mutableListOf<String>()
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
val pkg = getRecursiveField(iterator.next(), pkgNameObjList) as String?
|
||||
if (isToHide(caller, pkg)) {
|
||||
iterator.remove()
|
||||
isHidden = true
|
||||
if (config.detailLog) removed.add(pkg!!)
|
||||
}
|
||||
}
|
||||
|
||||
if (isHidden) {
|
||||
interceptionCount++
|
||||
Log.i("@Hide PMS caller: $caller method: ${param.method.name}")
|
||||
Log.d("removeList $removed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setResult(method: Method, hookMethod: String, result: Any?) {
|
||||
allHooks.add(method.hookAfter { param ->
|
||||
val caller = param.thisObject.getBinderCaller()
|
||||
if (!isUseHook(caller, hookMethod)) return@hookAfter
|
||||
|
||||
if (isToHide(caller, param.args[0] as String?)) {
|
||||
interceptionCount++
|
||||
param.result = result
|
||||
Log.i("@Hide PMS caller: $caller method: ${param.method.name} param: ${param.args[0]}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun resolveIntent(method: Method, hookMethod: String, result: Any?) {
|
||||
allHooks.add(method.hookAfter { param ->
|
||||
val caller = param.thisObject.getBinderCaller()
|
||||
if (!isUseHook(caller, hookMethod)) return@hookAfter
|
||||
|
||||
when (val it = param.args[0]) {
|
||||
is Intent -> it.component?.packageName
|
||||
is ComponentName -> it.packageName
|
||||
else -> null
|
||||
}?.let {
|
||||
if (isToHide(caller, it)) {
|
||||
interceptionCount++
|
||||
param.result = result
|
||||
Log.i("@Hide PMS caller: $caller method: ${param.method.name} param: ${param.args[0]}")
|
||||
return@hookAfter
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Hijack getInstallerPackageName as communication service */
|
||||
object HMAService : XC_MethodHook() {
|
||||
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||
val caller = param.thisObject.getBinderCaller()
|
||||
var arg = param.args[0] as String? ?: return
|
||||
|
||||
/* Non module calls require token validation */
|
||||
if (caller != hmaApp) {
|
||||
if (!arg.startsWith(token)) {
|
||||
if (!isUseHook(caller, "API requests")) return
|
||||
if (isToHide(caller, arg)) {
|
||||
interceptionCount++
|
||||
param.result = null
|
||||
Log.i("@Hide PMS caller: $caller method: ${param.method.name} param: ${param.args[0]}")
|
||||
}
|
||||
return
|
||||
} else arg = arg.removePrefix("$token#")
|
||||
}
|
||||
|
||||
when {
|
||||
arg == "getServiceVersion" ->
|
||||
param.result = BuildConfig.SERVICE_VERSION.toString()
|
||||
|
||||
arg == "getExtensionVersion" ->
|
||||
param.result = extensionVersion.toString()
|
||||
|
||||
arg == "getServeTimes" ->
|
||||
param.result = interceptionCount.toString()
|
||||
|
||||
arg == "getLogs" ->
|
||||
param.result = provideLogs()
|
||||
|
||||
arg == "cleanLogs" -> {
|
||||
synchronized(mLock) { logFile.apply { delete(); createNewFile() } }
|
||||
param.result = "OK"
|
||||
}
|
||||
|
||||
arg.startsWith("addLog") -> {
|
||||
addLog(arg.substring(7))
|
||||
param.result = "OK"
|
||||
}
|
||||
|
||||
arg.startsWith("submitConfig") -> {
|
||||
updateConfig(arg.split("#")[1])
|
||||
param.result = "OK"
|
||||
}
|
||||
|
||||
arg.startsWith("stopSystemService") -> {
|
||||
val split = arg.split("#")
|
||||
stopService(split[1] == "true")
|
||||
param.result = "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove all hooks */
|
||||
private fun stopService(cleanEnv: Boolean) {
|
||||
stopped = true
|
||||
File("$dataDir/tmp/ext_run").delete()
|
||||
Log.i("Receive stop system service signal")
|
||||
Log.i("Start to remove all hooks")
|
||||
for (hook in allHooks) {
|
||||
Log.i("Remove hook at ${hook.hookedMethod.name}")
|
||||
hook.unhook()
|
||||
}
|
||||
Log.i("System service stopped")
|
||||
synchronized(mLock) {
|
||||
if (cleanEnv) {
|
||||
Log.i("Clean runtime environment")
|
||||
File(dataDir).deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncWithExtension() {
|
||||
/* If extension not installed, make tmp by the service */
|
||||
File("$dataDir/tmp/ext_ver").apply {
|
||||
if (exists()) {
|
||||
var minApkVersion: Int
|
||||
try {
|
||||
val lines = readLines()
|
||||
extensionVersion = lines[0].toInt()
|
||||
minApkVersion = lines[1].toInt()
|
||||
} catch (e: Exception) {
|
||||
extensionVersion = 0
|
||||
minApkVersion = 0
|
||||
}
|
||||
if (extensionVersion < BuildConfig.MIN_EXTENSION_VERSION) {
|
||||
extensionVersion = -1
|
||||
File("$dataDir/tmp/ext_run").delete()
|
||||
Log.e("Magisk extension version too old to work with the new system service")
|
||||
}
|
||||
if (BuildConfig.VERSION_CODE < minApkVersion) {
|
||||
extensionVersion = -2
|
||||
File("$dataDir/tmp/ext_run").delete()
|
||||
Log.e("System service version too old to work with the new Magisk extension")
|
||||
}
|
||||
delete()
|
||||
} else File("$dataDir/tmp").apply {
|
||||
deleteRecursively()
|
||||
mkdirs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchDataDir() {
|
||||
File("/data/misc/hide_my_applist").deleteRecursively()
|
||||
File("/data/system").list()?.forEach {
|
||||
if (it.startsWith("hide_my_applist")) {
|
||||
if (this::dataDir.isInitialized) File("/data/system/$it").deleteRecursively()
|
||||
else dataDir = "/data/system/$it"
|
||||
}
|
||||
}
|
||||
if (!this::dataDir.isInitialized) {
|
||||
dataDir = "/data/system/hide_my_applist_" + generateRandomString(16)
|
||||
}
|
||||
logFile = File("$dataDir/tmp/runtime.log")
|
||||
}
|
||||
|
||||
/* Load system service */
|
||||
fun entry() {
|
||||
searchDataDir()
|
||||
syncWithExtension()
|
||||
generateToken()
|
||||
try {
|
||||
initConfig()
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.i("Config not cached, waiting for preference provider")
|
||||
} catch (e: Exception) {
|
||||
config = JsonConfig()
|
||||
configStr = config.toString()
|
||||
Log.e("Failed to read cached config, waiting for preference provider\n${e.stackTraceToString()}")
|
||||
}
|
||||
thread {
|
||||
while (!stopped) {
|
||||
if (configCached) synchronized(mLock) {
|
||||
if (stopped) return@thread
|
||||
File("$dataDir/interception_cnt").writeText(interceptionCount.toString())
|
||||
}
|
||||
Thread.sleep(2000)
|
||||
}
|
||||
}
|
||||
|
||||
val pms = loadClass("com.android.server.pm.PackageManagerService")
|
||||
allHooks.addAll(pms.hookAllConstructorAfter { param ->
|
||||
/* Cache system app list */
|
||||
val mSettings = param.thisObject.getObject("mSettings")
|
||||
val mPackages = mSettings.getObjectAs<Map<String, *>>("mPackages")
|
||||
for ((name, ps) in mPackages) {
|
||||
if (ps != null && (ps.getObjectAs<Int>("pkgFlags") and ApplicationInfo.FLAG_SYSTEM != 0)) {
|
||||
systemApps.add(name)
|
||||
}
|
||||
}
|
||||
for (pkg in systemApps)
|
||||
File("$dataDir/tmp/system_apps.list").appendText("$pkg\n")
|
||||
|
||||
Log.i("System hook installed (Version ${BuildConfig.SERVICE_VERSION})")
|
||||
Log.i("Data directory is at $dataDir")
|
||||
})
|
||||
|
||||
/* ---Deal with 💩 ROMs--- */
|
||||
var extPms: Class<*>? = null
|
||||
for (clazz in customPms) {
|
||||
try {
|
||||
extPms = loadClass(clazz)
|
||||
break
|
||||
} catch (e: ClassNotFoundException) {
|
||||
}
|
||||
}
|
||||
val pmMethods = mutableSetOf<Method>()
|
||||
val methodNames = mutableSetOf<String>()
|
||||
if (extPms != null) {
|
||||
allHooks.addAll(extPms.hookAllConstructorAfter { param ->
|
||||
Log.i("Non-AOSP PMS ${param.method.declaringClass.name}")
|
||||
})
|
||||
for (method in extPms.declaredMethods) {
|
||||
pmMethods.add(method)
|
||||
methodNames.add(method.name)
|
||||
}
|
||||
}
|
||||
/* ----------------------- */
|
||||
for (method in pms.declaredMethods)
|
||||
if (method.name !in methodNames)
|
||||
pmMethods.add(method)
|
||||
|
||||
for (method in pmMethods) when (method.name) {
|
||||
"getInstallerPackageName"
|
||||
-> allHooks.add(XposedBridge.hookMethod(method, HMAService))
|
||||
|
||||
"getAllPackages"
|
||||
-> removeList(method, false, "API requests", listOf())
|
||||
|
||||
"getInstalledPackages",
|
||||
"getInstalledApplications",
|
||||
"getPackagesHoldingPermissions",
|
||||
"queryInstrumentation"
|
||||
-> removeList(method, true, "API requests", listOf("packageName"))
|
||||
|
||||
"getPackageInfo",
|
||||
"getPackageGids",
|
||||
"getApplicationInfo",
|
||||
"getInstallSourceInfo",
|
||||
"getLaunchIntentForPackage",
|
||||
"getLeanbackLaunchIntentForPackage"
|
||||
-> setResult(method, "API requests", null)
|
||||
|
||||
"queryIntentActivities",
|
||||
"queryIntentActivityOptions",
|
||||
"queryIntentReceivers",
|
||||
"queryIntentServices",
|
||||
"queryIntentContentProviders"
|
||||
-> removeList(method, true, "Intent queries", listOf("activityInfo", "packageName"))
|
||||
|
||||
"getActivityInfo",
|
||||
"resolveActivity",
|
||||
"resolveActivityAsUser"
|
||||
-> resolveIntent(method, "Intent queries", null)
|
||||
|
||||
"getPackageUid"
|
||||
-> setResult(method, "ID detections", -1)
|
||||
|
||||
"getPackagesForUid"
|
||||
-> allHooks.add(method.hookAfter { param ->
|
||||
if (param.hasThrowable()) return@hookAfter
|
||||
val caller = param.thisObject.getBinderCaller()
|
||||
if (!isUseHook(caller, "ID detections")) return@hookAfter
|
||||
if (param.result != null) {
|
||||
var change = false
|
||||
val list = mutableListOf<String>()
|
||||
val removed = mutableListOf<String>()
|
||||
for (str in param.result as Array<String>)
|
||||
if (isToHide(caller, str)) {
|
||||
change = true
|
||||
if (config.detailLog) removed.add(str)
|
||||
} else list.add(str)
|
||||
if (change) {
|
||||
interceptionCount++
|
||||
param.result = list.toTypedArray()
|
||||
Log.i("@Hide PMS caller: $caller method: ${param.method.name}")
|
||||
Log.d("removeList $removed")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue