3.0.0 Dev: Rewrite backend & Drop Magisk extension

This commit is contained in:
Nullptr 2022-06-29 00:11:12 +08:00
commit e38f1054f3
No known key found for this signature in database
GPG key ID: 0B9D02052FF536BD
49 changed files with 865 additions and 1134 deletions

View file

@ -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}'`"

View file

@ -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"

View file

@ -1,7 +1,5 @@
# Hide My Applist
## This project is suspended 此项目已停更
[![Stars](https://img.shields.io/github/stars/Dr-TSNG/Hide-My-Applist?label=Stars)](https://github.com/Dr-TSNG)
[![Release](https://img.shields.io/github/v/release/Dr-TSNG/Hide-My-Applist?label=Release)](https://github.com/Dr-TSNG/Hide-My-Applist/releases/latest)
[![Download](https://img.shields.io/github/downloads/Dr-TSNG/Hide-My-Applist/total)](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.

View file

@ -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")
}

View file

@ -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.

View file

@ -1 +1 @@
com.tsng.hidemyapplist.xposed.XposedEntry
icu.nullptr.hidemyapplist.xposed.XposedEntry

View file

@ -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()
}
}

View file

@ -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 {
}
}
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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"))
}
}
}

View file

@ -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) {

View file

@ -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
}
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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() {
}
}
}
}
}

View file

@ -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() }
}
}
}
}
}

View file

@ -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