diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e6902b2c..11eca0f52 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -69,7 +69,8 @@ jobs: - name: Build all id: buildAll run: | - ./gradlew assemble + ./gradlew :app:assemble + ./gradlew :zygote:assemble echo "releaseName=$(ls app/build/outputs/apk/release/*.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT echo "debugName=$(ls app/build/outputs/apk/debug/*.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT echo "releaseFile=$(ls app/build/outputs/apk/release/*.apk)" >> $GITHUB_OUTPUT diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index df6458b64..65d6de674 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -60,11 +60,23 @@ jobs: id: buildDebug run: | ./gradlew assembleDebug - echo "debugName=$(ls app/build/outputs/apk/debug/*.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT + echo "debugAPKName=$(ls app/build/outputs/apk/debug/*.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT + echo "debugZIPName=$(ls zygote/build/outputs/magisk/debug/*.zip | awk -F '(/|.zip)' '{print $6}')" >> $GITHUB_OUTPUT - - name: Upload debug + - name: Upload debug APK if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: - name: ${{ steps.buildDebug.outputs.debugName }} - path: "app/build/outputs/apk/debug/*.apk" + archive: false + name: ${{ steps.buildDebug.outputs.debugAPKName }} + path: | + app/build/outputs/apk/debug/*.apk + + - name: Upload debug ZIP + if: success() + uses: actions/upload-artifact@v7 + with: + archive: false + name: ${{ steps.buildDebug.outputs.debugZIPName }} + path: | + zygote/build/outputs/magisk/debug/*.zip diff --git a/.gitignore b/.gitignore index bd436ac56..208a0004e 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,6 @@ lint/tmp/ updates/ translators.json + +.kotlin/ +xposed/ diff --git a/CREDITS.md b/CREDITS.md index 32e178516..3a3a2ddd1 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -5,6 +5,7 @@ - [IAmNotADeveloper](https://github.com/xfqwdsj/IAmNotADeveloper) - hiding developer options idea - [BetterKnownInstalled](https://github.com/Pixel-Props/BetterKnownInstalled) - package installer spoofing idea - [0bbedCode](https://github.com/0bbedCode) - ID checker +- [vova7878](https://github.com/vova7878) - ZygoteLoader, AndroidVMTools and PanamaPort - All translators - All root community - You (if you are not a robot 🤖) diff --git a/README.md b/README.md index e4c06ae0c..1e80ee4b7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Although it's bad practice to detect the installation of specific apps, not ever Additionally, some apps use various loopholes to acquire your app list, in order to use it as fingerprinting data or for other nefarious purposes. -This module can work as an Xposed module to hide apps or reject app list requests. +This module can work as an Zygisk module to hide apps or reject app list requests. ## About HMA-OSS diff --git a/README_id.md b/README_id.md index ae6e66e14..f79da5981 100644 --- a/README_id.md +++ b/README_id.md @@ -45,7 +45,7 @@ Meskipun merupakan praktik yang buruk untuk mendeteksi pemasangan aplikasi terte Selain itu, beberapa aplikasi menggunakan berbagai celah untuk memperoleh daftar aplikasi anda, untuk menggunakannya sebagai data fingerprinting atau untuk tujuan lain yang tidak diinginkan. -Modul ini dapat berfungsi sebagai modul Xposed untuk menyembunyikan aplikasi atau menolak permintaan daftar aplikasi. +Modul ini dapat berfungsi sebagai modul Zygisk untuk menyembunyikan aplikasi atau menolak permintaan daftar aplikasi. ## Saya ingin berkontribusi dalam terjemahan Anda dapat berkontribusi dalam penerjemahan [Di Sini](https://crowdin.com/project/frknkrc44-hma-oss). diff --git a/README_ja.md b/README_ja.md index 16cbd172f..ce182b6ca 100644 --- a/README_ja.md +++ b/README_ja.md @@ -45,7 +45,7 @@ さらに、一部のアプリはさまざまな抜け穴を利用してアプリリストを取得し、それをフィンガープリンティングデータとして使用したり、その他の不正な目的に使用したりします。 -このモジュールは、アプリを非表示にしたり、アプリリストの要求を拒否したりする Xposed モジュールとして機能します。 +このモジュールは、アプリを非表示にしたり、アプリリストの要求を拒否したりする Zygisk モジュールとして機能します。 ## 翻訳に貢献する [こちら](https://crowdin.com/project/frknkrc44-hma-oss)から翻訳に貢献することができます。 diff --git a/README_tr.md b/README_tr.md index e03ad8356..7c1d07f82 100644 --- a/README_tr.md +++ b/README_tr.md @@ -45,7 +45,7 @@ Belirli uygulamaların kurulu olup olmadığını tespit etmek kötü bir yönte Ayrıca bazı uygulamalar uygulama listenizi ele geçirmek, parmak izi verileri olarak kullanmak veya başka kötü niyetli amaçlar için çeşitli açıklardan yararlanır. -Bu modül, uygulamaları gizlemek veya uygulama listesi isteklerini reddetmek için çalışabilen bir Xposed modülüdür. +Bu modül, uygulamaları gizlemek veya uygulama listesi isteklerini reddetmek için çalışabilen bir Zygisk modülüdür. ## Çeviriye katkıda bulunmak istiyorum [Buraya tıklayarak](https://crowdin.com/project/frknkrc44-hma-oss) çeviriye katkıda bulunabilirsiniz. diff --git a/README_zh_CN.md b/README_zh_CN.md index 1895672ca..da1df1b8e 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -42,7 +42,7 @@ ## 关于该模块 虽然“检测安装的应用”是不正确的做法,但是并不是所有的与 root 相关联的插件类应用都提供了随机包名支持。这就意味着检测到安装了此类应用(如 Fake Location 、存储空间隔离)与检测到了 root 本身区别不大。(会使用检测手段的 app 可不会认为你是在“我就蹭蹭不进去”) 与此同时,部分“不安分”的应用会使用各种漏洞绕过系统权限来获取你的应用列表,从而对你建立用户画像。(如陈叔叔将安装了 V2Ray 的用户分为一类),或是类似于某某校园某某乐跑的软件会要求你卸载作弊软件。 -该模块提供了一些检测方式用于测试您是否成功地隐藏了某些特定的包名,如 Magisk/Edxposed Manager;同时可作为 Xposed 模块用于隐藏应用列表或特定应用,保护隐私。 +该模块提供了一些检测方式用于测试您是否成功地隐藏了某些特定的包名,如 Magisk Manager;同时可作为 Zygisk 模块用于隐藏应用列表或特定应用,保护隐私。 ## 更新日志 [参考发布页面](https://github.com/frknkrc44/HMA-OSS/commits) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d2635004b..2f1146f06 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,3 @@ -import com.android.build.gradle.internal.api.BaseVariantOutputImpl import com.google.gson.JsonParser import org.jose4j.json.internal.json_simple.JSONObject import java.io.DataInputStream @@ -7,7 +6,6 @@ import java.net.URL plugins { alias(libs.plugins.agp.app) - alias(libs.plugins.autoresconfig) alias(libs.plugins.refine) alias(libs.plugins.kotlin) alias(libs.plugins.kotlin.serialization) @@ -117,11 +115,19 @@ afterEvaluate { android { namespace = appPackageName + defaultConfig { + buildConfigField("String[]", "SUPPORTED_LOCALES", generateSupportedLocales()) + } + buildFeatures { buildConfig = true viewBinding = true } + base { + archivesName = "${rootProject.name}-${defaultConfig.versionName!!.replace("/", "_")}" + } + packaging { dex.useLegacyPackaging = true resources { @@ -139,22 +145,45 @@ kotlin { jvmToolchain(21) } -autoResConfig { - generateClass.set(true) - generateRes.set(false) - generatedClassFullName.set("icu.nullptr.hidemyapplist.util.LangList") - generatedArrayFirstItem.set("SYSTEM") +// Inspired from https://github.com/XayahSuSuSu/Android-DataBackup/pull/260 +fun generateSupportedLocales(): String { + val foundLocales = StringBuilder() + foundLocales.append("new String[]{") + + fun appendLangCode(code: String) { + foundLocales.append("\"").append(code).append("\"").append(",") + } + + appendLangCode("SYSTEM") + + fileTree(android.sourceSets["main"].res.srcDirs.first()).files.mapNotNull { + if (it.name == "strings.xml") { + val baseName = it.parent.substringAfterLast(File.separator) + if (baseName == "values") { + "en" + } else { + baseName.substringAfter('-') + .replace("-r", "-") + } + } else { + null + } + }.sortedWith { file1, file2 -> + file1.compareTo(file2) + }.forEach { appendLangCode(it) } + + return "${foundLocales.removeSuffix(",")}}" } dependencies { implementation(projects.common) - runtimeOnly(projects.xposed) implementation(libs.androidx.navigation.fragment.ktx) implementation(libs.androidx.navigation.ui.ktx) implementation(libs.androidx.preference.ktx) implementation(libs.androidx.swiperefreshlayout) - implementation(libs.com.github.bumptech.glide) + implementation(libs.io.coilkt.coil3.coil) + implementation(libs.io.coilkt.coil3.coil.network.okhttp) implementation(libs.dev.androidbroadcast.vbpd) implementation(libs.dev.androidbroadcast.vbpd.reflection) implementation(libs.com.github.topjohnwu.libsu.core) @@ -165,11 +194,3 @@ dependencies { implementation(libs.androidx.appcompat.appcompat) implementation(libs.material) } - -android.applicationVariants.all { - outputs.all { - (this as BaseVariantOutputImpl).apply { - outputFileName = "${rootProject.name.replace(" ", "_")}-${versionName}-${buildType.name}.apk" - } - } -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index d5645a4a9..91fee77c4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -4,9 +4,6 @@ public static ** valueOf(java.lang.String); } --keep class icu.nullptr.hidemyapplist.data.UpdateData { *; } --keep class icu.nullptr.hidemyapplist.data.UpdateData$* { *; } - -keep,allowoptimization class * extends androidx.preference.PreferenceFragmentCompat -keepclassmembers class org.frknkrc44.hma_oss.databinding.** { public ; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c69358f2b..f8d68b73c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,12 +22,7 @@ android:name="org.frknkrc44.hma_oss.ui.activity.MainActivity" android:exported="true" android:configChanges="assetsPaths|colorMode|density|fontScale|fontWeightAdjustment|grammaticalGender|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|resourcesUnused|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" - android:windowSoftInputMode="adjustPan"> - - - - - + android:windowSoftInputMode="adjustPan" /> - - - - diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/MyApp.kt b/app/src/main/java/icu/nullptr/hidemyapplist/MyApp.kt index 8710625e0..da7cfdd71 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/MyApp.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/MyApp.kt @@ -8,13 +8,11 @@ import icu.nullptr.hidemyapplist.receiver.AppChangeReceiver import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.service.ServiceClient -import icu.nullptr.hidemyapplist.ui.util.showToast import icu.nullptr.hidemyapplist.util.ConfigUtils.Companion.getLocale import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import me.zhanghai.android.appiconloader.AppIconLoader import org.frknkrc44.hma_oss.R -import kotlin.system.exitProcess class MyApp : Application() { companion object { @@ -33,10 +31,6 @@ class MyApp : Application() { override fun onCreate() { super.onCreate() hmaApp = this - if (!filesDir.absolutePath.startsWith("/data/user/0/")) { - showToast(R.string.do_not_dual) - exitProcess(0) - } AppChangeReceiver.register(this) ConfigManager.init() diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt index 5d1c64377..e17dcacef 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt @@ -4,6 +4,7 @@ import android.os.Build import android.os.ParcelFileDescriptor import android.util.Log import icu.nullptr.hidemyapplist.MyApp.Companion.hmaApp +import icu.nullptr.hidemyapplist.common.CollectionUtils.removeIfWithCount import icu.nullptr.hidemyapplist.common.Constants import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem @@ -42,8 +43,13 @@ object ConfigManager { fun init() { val configFileIsNew = !configFile.exists() if (configFileIsNew) { - config = JsonConfig() - configFile.writeText(config.toString()) + runCatching { + val rawConfig = ServiceClient.readConfig()!! + config = JsonConfig.parse(rawConfig) + }.onFailure { + config = JsonConfig() + configFile.writeText(config.toString()) + } } runCatching { if (!configFileIsNew) config = JsonConfig.parse(configFile.readText()) @@ -145,6 +151,15 @@ object ConfigManager { PackageHelper.invalidateCache() } + var disabledHooks: List + get() = config.disabledHooks + set(elements) { + config.disabledHooks.clear() + config.disabledHooks.addAll(elements) + saveConfig() + showToast(R.string.settings_need_reboot) + } + fun importConfig(json: String) { config = JsonConfig.parse(json) config.configVersion = BuildConfig.CONFIG_VERSION @@ -269,38 +284,31 @@ object ConfigManager { saveConfig() } - fun clearUninstalledAppConfigs(onFinish: (success: Boolean) -> Unit) { + fun clearUninstalledAppConfigs(inConfig: JsonConfig = config, onFinish: (success: Boolean) -> Unit) { PackageHelper.invalidateCache { throwable -> if (throwable == null) { // --- STEP 1: Clear uninstalled app configs --- - val scopeMarkedToRemove = mutableListOf() - config.scope.keys.forEach { packageName -> - if (!PackageHelper.exists(packageName)) { - scopeMarkedToRemove.add(packageName) - } - } - - if (scopeMarkedToRemove.isNotEmpty()) { - scopeMarkedToRemove.forEach { config.scope.remove(it) } + val scopeRemoveCount = inConfig.scope.removeIfWithCount { pkg, _ -> + !PackageHelper.exists(pkg) } // --- STEP 2: Clear uninstalled apps from templates --- var cleanedAppCount = 0 - config.templates.forEach { (key, value) -> + inConfig.templates.forEach { (key, value) -> val newList = value.appList.mapNotNull { if (PackageHelper.exists(it)) it else null }.toSet() val count = value.appList.size - newList.size if (count > 0) { cleanedAppCount += count - config.templates[key] = JsonConfig.Template( + inConfig.templates[key] = JsonConfig.Template( isWhitelist = value.isWhitelist, appList = newList ) } } - ServiceClient.log(Log.INFO, TAG, "Pruned ${scopeMarkedToRemove.size} app config(s) and $cleanedAppCount app(s) from template(s)") - if (scopeMarkedToRemove.isNotEmpty() || cleanedAppCount > 0) { + if ((scopeRemoveCount > 0 || cleanedAppCount > 0) && inConfig == config) { + ServiceClient.log(Log.INFO, TAG, "Pruned $scopeRemoveCount app config(s) and $cleanedAppCount app(s) from template(s)") saveConfig() } @@ -310,4 +318,20 @@ object ConfigManager { } } } + + fun getRawConfig(deepCopy: Boolean): JsonConfig { + if (deepCopy) { + val scopeCopy = config.scope.toMutableMap() + val templateCopy = config.templates.toMutableMap() + val settingsTemplateCopy = config.settingsTemplates.toMutableMap() + + return config.copy( + scope = scopeCopy, + templates = templateCopy, + settingsTemplates = settingsTemplateCopy, + ) + } + + return config + } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt index f59ada051..05030e005 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt @@ -108,12 +108,12 @@ object PrefManager { set(value) = pref.edit { putInt(PREF_ENABLE_INTERNET, value) } fun setEnableInternet(value: Boolean) { - enableInternet = if (value) { - Constants.ENABLE_INTERNET_ON - } else { - Constants.ENABLE_INTERNET_OFF + enableInternet = if (value) { + Constants.ENABLE_INTERNET_ON + } else { + Constants.ENABLE_INTERNET_OFF + } } - } var disableUpdate: Boolean get() = pref.getBoolean(PREF_DISABLE_UPDATE, false) diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt index b66f43c6d..725e30364 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt @@ -125,6 +125,8 @@ object ServiceClient : IHMAService, IBinder.DeathRecipient { service?.serviceVersionName } catch (_: Throwable) { null } + override fun getLoadedHooks() = service?.loadedHooks + override fun readFD(type: Int) = service?.readFD(type) override fun writeFD(type: Int, fd: ParcelFileDescriptor) { diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/LogsFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/LogsFragment.kt index 473fed91a..6ff228eb9 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/LogsFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/LogsFragment.kt @@ -16,9 +16,6 @@ import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.service.ServiceClient import icu.nullptr.hidemyapplist.ui.adapter.LogAdapter import icu.nullptr.hidemyapplist.ui.util.contentResolver -import icu.nullptr.hidemyapplist.ui.util.navController -import icu.nullptr.hidemyapplist.ui.util.setEdge2EdgeFlags -import icu.nullptr.hidemyapplist.ui.util.setupToolbar import icu.nullptr.hidemyapplist.ui.util.showToast import kotlinx.coroutines.launch import org.frknkrc44.hma_oss.R @@ -28,7 +25,7 @@ import java.util.Date import java.util.Locale -class LogsFragment : Fragment(R.layout.fragment_logs) { +class LogsFragment(private val loadingIndicator: View) : Fragment(R.layout.fragment_logs) { private val binding by viewBinding(FragmentLogsBinding::bind) private val adapter by lazy { LogAdapter(requireContext()) } @@ -62,7 +59,7 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { if (binding.serviceOff.isVisible) return - binding.loadingIndicator.isVisible = true + loadingIndicator.isVisible = true MyApp.hmaApp.globalScope.launch { logCache = try { @@ -93,15 +90,15 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { } lifecycleScope.launch { - binding.loadingIndicator.visibility = View.INVISIBLE + loadingIndicator.visibility = View.INVISIBLE adapter.logs = logList } } } } - private fun onMenuOptionSelected(item: MenuItem) { - if (binding.loadingIndicator.isVisible) return + fun onMenuOptionSelected(item: MenuItem) { + if (loadingIndicator.isVisible) return when (item.itemId) { R.id.menu_refresh -> updateLogs() @@ -142,33 +139,9 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - with(binding.toolbar) { - setupToolbar( - toolbar = this, - title = getString(R.string.title_logs), - menuRes = R.menu.menu_logs, - onMenuOptionSelected = this@LogsFragment::onMenuOptionSelected - ) - setNavigationIcon(R.drawable.baseline_arrow_back_24) - setNavigationOnClickListener { navController.popBackStack() } - // isTitleCentered = true - } - - with(binding.toolbar.menu) { - when (PrefManager.logFilter_level) { - 0 -> findItem(R.id.menu_filter_debug).isChecked = true - 1 -> findItem(R.id.menu_filter_info).isChecked = true - 2 -> findItem(R.id.menu_filter_warn).isChecked = true - 3 -> findItem(R.id.menu_filter_error).isChecked = true - } - findItem(R.id.menu_reverse_order).isChecked = PrefManager.logFilter_reverseOrder - } - binding.list.layoutManager = LinearLayoutManager(context) binding.list.adapter = adapter binding.list.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) updateLogs() - - setEdge2EdgeFlags(binding.root) } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/SettingsFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/SettingsFragment.kt index ec146fb3a..74ab006e2 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/SettingsFragment.kt @@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference import androidx.preference.Preference import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceFragmentCompat @@ -21,6 +22,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dev.androidbroadcast.vbpd.viewBinding import icu.nullptr.hidemyapplist.MyApp.Companion.hmaApp import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.PropertyUtils import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.service.PrefManager @@ -33,11 +35,11 @@ import icu.nullptr.hidemyapplist.ui.util.setupToolbar import icu.nullptr.hidemyapplist.ui.util.showToast import icu.nullptr.hidemyapplist.ui.util.withAnimations import icu.nullptr.hidemyapplist.util.ConfigUtils.Companion.getLocale -import icu.nullptr.hidemyapplist.util.LangList import icu.nullptr.hidemyapplist.util.PackageHelper.findEnabledAppComponent import icu.nullptr.hidemyapplist.util.SuUtils import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking +import org.frknkrc44.hma_oss.BuildConfig import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentSettingsBinding import org.frknkrc44.hma_oss.ui.activity.MainActivity @@ -121,6 +123,13 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen } } + override fun getStringSet(key: String?, defValues: Set?): Set { + return when (key) { + "disableHooks" -> ConfigManager.disabledHooks.map { it.toString() }.toSet() + else -> throw IllegalArgumentException("Invalid key: $key") + } + } + override fun putBoolean(key: String, value: Boolean) { when (key) { "followSystemAccent" -> PrefManager.followSystemAccent = value @@ -158,6 +167,13 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen else -> throw IllegalArgumentException("Invalid key: $key") } } + + override fun putStringSet(key: String, values: Set?) { + when (key) { + "disableHooks" -> ConfigManager.disabledHooks = values?.map { JsonConfig.HookItem.parse(it) } ?: listOf() + else -> throw IllegalArgumentException("Invalid key: $key") + } + } } class DataIsolationPreferenceFragment(private val preferenceDataStore: PreferenceDataStore) : PreferenceFragmentCompat() { @@ -241,7 +257,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen findPreference("language")?.let { val userLocale = getLocale() val entries = buildList { - for (lang in LangList.LOCALES) { + for (lang in BuildConfig.SUPPORTED_LOCALES) { if (lang == "SYSTEM") add(getString(R.string.follow_system)) else { val locale = Locale.forLanguageTag(lang) @@ -250,7 +266,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen } } it.entries = entries.toTypedArray() - it.entryValues = LangList.LOCALES + it.entryValues = BuildConfig.SUPPORTED_LOCALES if (it.value == "SYSTEM") { it.summary = getString(R.string.follow_system) } else { @@ -393,6 +409,23 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen configureDataIsolation() + findPreference("disableHooks")?.apply { + val allHooks = (ConfigManager.disabledHooks + (ServiceClient.loadedHooks?.map { JsonConfig.HookItem.parse(it) } ?: listOf())).let { + it.sortedWith { item1, item2 -> + fun JsonConfig.HookItem.comparator() = "${className.substringAfterLast('.')}##$methodName" + + item1.comparator().compareTo(item2.comparator()) + } + } + + entries = allHooks.map { + val displayedArgCount = if (it.argumentCount >= 0) { "${it.argumentCount} args" } else { "..." } + + "${it.className.substringAfterLast('.')} -> ${it.methodName}($displayedArgCount)" + }.toTypedArray() + entryValues = allHooks.map { it.toString() }.toTypedArray() + } + findPreference("stopSystemService")?.setOnPreferenceClickListener { if (ServiceClient.serviceVersion != 0) { MaterialAlertDialogBuilder(requireContext()) diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Common.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Common.kt index 7f6d930e5..08845b338 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Common.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Common.kt @@ -20,4 +20,8 @@ fun MutableSharedFlow.get() = replayCache.first() fun dp2Px(res: Resources, dp: Int) = res.displayMetrics.density * dp -val isTestBuild get() = BuildConfig.VERSION_NAME.count { it == '-' } != 1 +val isTestBuild get() = BuildConfig.VERSION_NAME.let { name -> + name.count { it == '-' } != 1 || + name.count { it == '+' } > 0 || + name.split('-').last().length == 8 +} diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt index 19d195eb7..9a2156c50 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt @@ -105,6 +105,7 @@ fun Fragment.setupToolbar( } ?: false } } + toolbar.menu.clear() toolbar.inflateMenu(menuRes) toolbar.setOnMenuItemClickListener(menuProvider::onMenuItemSelected) requireActivity().addMenuProvider(menuProvider) diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/StatAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/StatAdapter.kt index 80cb76833..1846c288e 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/StatAdapter.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/StatAdapter.kt @@ -7,7 +7,7 @@ import icu.nullptr.hidemyapplist.common.FilterHolder import icu.nullptr.hidemyapplist.util.PackageHelper import org.frknkrc44.hma_oss.databinding.StatItemViewBinding -class StatAdapter() : RecyclerView.Adapter() { +class StatAdapter(private val onBeginWaitForRefresh: (StatAdapter) -> Unit) : RecyclerView.Adapter() { data class StatItem( val packageName: String, @@ -19,18 +19,22 @@ class StatAdapter() : RecyclerView.Adapter() { private val logs = mutableListOf() + var wasRefreshing = false + internal fun addOrUpdateEntry(packageName: String, filterCount: FilterHolder.FilterCount) { val position = logs.indexOfFirst { it.packageName == packageName } + val refreshing = PackageHelper.refreshing + if (position < 0) { - logs.add(StatItem(packageName, filterCount, PackageHelper.refreshing)) + logs.add(StatItem(packageName, filterCount, refreshing)) notifyItemInserted(logs.size - 1) } else { val item = logs[position] if (item.totalCount == filterCount.totalCount && !item.refreshing) return - logs[position] = StatItem(packageName, filterCount, PackageHelper.refreshing) + logs[position] = StatItem(packageName, filterCount, refreshing) - val resort = logs.sortedWith { it1, it2 -> if (it1.totalCount > it2.totalCount) -1 else 0 } + val resort = logs.sortedWith { it1, it2 -> it1.totalCount.compareTo(it2.totalCount) }.asReversed() val newIndex = resort.indexOfFirst { it.packageName == packageName } logs.clear() @@ -43,6 +47,12 @@ class StatAdapter() : RecyclerView.Adapter() { notifyItemChanged(position) } } + + if (!wasRefreshing && refreshing) { + wasRefreshing = true + + onBeginWaitForRefresh(this) + } } internal fun clearEntriesIfNotFound(packageNames: Iterable) { diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt index 3fbf11acc..df48bb790 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt @@ -13,10 +13,14 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.transition.AutoTransition import androidx.transition.TransitionManager -import com.bumptech.glide.Glide +import coil3.load +import coil3.request.crossfade +import coil3.request.placeholder +import coil3.request.transformations +import coil3.transform.CircleCropTransformation import dev.androidbroadcast.vbpd.viewBinding import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.data.AppConstants.allAppIcons import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.ui.util.AccessibilityUtils import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.homeItemBackgroundColor @@ -54,14 +58,12 @@ class AboutFragment : Fragment(R.layout.fragment_about) { backgroundTintList = tint } - Glide.with(this@AboutFragment).let { - val activityName = findEnabledAppComponent(requireContext()) - return@let if (activityName == null) { - it.load(R.mipmap.ic_launcher) - } else { - it.load(requireContext().packageManager.getActivityIcon(activityName)) - } - }.circleCrop().into(appIcon) + val activityName = findEnabledAppComponent(requireContext()) + appIcon.setImageResource( + activityName?.let { + allAppIcons.firstOrNull { it.second == activityName.className }?.first + } ?: R.mipmap.ic_launcher + ) appName.setText(R.string.app_name) appVersion.text = BuildConfig.APP_VERSION_NAME @@ -139,8 +141,8 @@ class AboutFragment : Fragment(R.layout.fragment_about) { backgroundTintList = tint clipToOutline = true - addLibraryItem(this, "EzXHelper", "Apache Software License 2.0", "https://github.com/KyuubiRan/EzXHelper") - addLibraryItem(this, "Glide", "Simplified BSD License", "https://github.com/bumptech/glide") + addLibraryItem(this, "ZygoteLoader (fork)", "MIT License", "https://github.com/aerath-stuff/ZygoteLoader") + addLibraryItem(this, "Coil", "Apache-2.0 License", "https://github.com/coil-kt/coil") } } @@ -174,11 +176,14 @@ class AboutFragment : Fragment(R.layout.fragment_about) { val newLayout = FragmentAboutListItemBinding.inflate(layoutInflater) if (PrefManager.enableInternet == Constants.ENABLE_INTERNET_ON) { - Glide.with(this) - .load(avatarUrl) - .placeholder(R.drawable.outline_info_24) - .circleCrop() - .into(newLayout.aboutPersonIcon) + newLayout.aboutPersonIcon.load( + avatarUrl, + builder = { + crossfade(true) + placeholder(R.drawable.outline_info_24) + transformations(CircleCropTransformation()) + } + ) } else { newLayout.aboutPersonIcon.isVisible = false } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BackupRestoreFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BackupRestoreFragment.kt new file mode 100644 index 000000000..1b2d3a7ed --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BackupRestoreFragment.kt @@ -0,0 +1,382 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.annotation.SuppressLint +import android.icu.text.SimpleDateFormat +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.navArgs +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dev.androidbroadcast.vbpd.viewBinding +import icu.nullptr.hidemyapplist.common.CollectionUtils.removeIf +import icu.nullptr.hidemyapplist.common.Constants.CONFIG_VERSION_NO_SETTINGS +import icu.nullptr.hidemyapplist.common.JsonConfig +import icu.nullptr.hidemyapplist.common.Utils.cleanRemnantsFromConfig +import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.ui.util.contentResolver +import icu.nullptr.hidemyapplist.ui.util.navController +import icu.nullptr.hidemyapplist.ui.util.setEdge2EdgeFlags +import icu.nullptr.hidemyapplist.ui.util.setupToolbar +import icu.nullptr.hidemyapplist.ui.util.showToast +import icu.nullptr.hidemyapplist.util.PackageHelper.loadAppLabel +import kotlinx.coroutines.launch +import org.frknkrc44.hma_oss.R +import org.frknkrc44.hma_oss.databinding.FragmentBackupRestoreBinding +import org.frknkrc44.hma_oss.databinding.LayoutListEmptyBinding +import java.util.Date +import java.util.Locale + +class BackupRestoreFragment : Fragment(R.layout.fragment_backup_restore) { + + private val binding by viewBinding(FragmentBackupRestoreBinding::bind) + + private val args by lazy { navArgs() } + + private lateinit var importedConfig: JsonConfig + + private val isBackupMode by lazy { args.value.isBackupMode } + private val includeSettings get() = binding.switchSettings.isChecked + private val trimConfig get() = binding.switchTrimConfig.isChecked + private val overwriteApps get() = binding.switchOverwriteApps.isChecked + private val overwriteTemplates get() = binding.switchOverwriteTemplates.isChecked + private val overwriteSettingsTemplates get() = binding.switchOverwriteTemplates.isChecked + + private enum class BRCategory { + APP, + TEMPLATE, + SETTINGS_TEMPLATE, + } + + private val markedForBackup = BRCategory.entries.associateWith { mutableSetOf() } + + private val backupSAFLauncher = + registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) backup@{ uri -> + if (uri == null) return@backup + val output = contentResolver.openOutputStream(uri) + + if (output == null) showToast(R.string.home_export_failed) + else { + clearNotImportedItems { + if (!includeSettings) { + val newConfig = JsonConfig(CONFIG_VERSION_NO_SETTINGS) + newConfig.templates.putAll(importedConfig.templates) + newConfig.settingsTemplates.putAll(importedConfig.settingsTemplates) + newConfig.scope.putAll(importedConfig.scope) + importedConfig = newConfig + } + + showToast(R.string.home_exported) + output.write(importedConfig.toString().toByteArray()) + output.close() + + navController.navigateUp() + } + } + } + + private val restoreSAFLauncher = + registerForActivityResult(ActivityResultContracts.GetContent()) restore@{ uri -> + if (uri == null) { + navController.navigateUp() + return@restore + } + + runCatching { + val backupContent = contentResolver + .openInputStream(uri)!!.reader().use { it.readText() } + importedConfig = JsonConfig.parse(backupContent) + loadScreenContents() + }.onFailure { + it.printStackTrace() + navController.navigateUp() + MaterialAlertDialogBuilder(requireContext()) + .setCancelable(false) + .setTitle(R.string.home_import_failed) + .setMessage(it.message) + .setPositiveButton(android.R.string.ok, null) + .setNegativeButton(R.string.show_crash_log) { _, _ -> + MaterialAlertDialogBuilder(requireActivity()) + .setCancelable(false) + .setTitle(R.string.home_import_failed) + .setMessage(it.stackTraceToString()) + .setPositiveButton(android.R.string.ok, null) + .show() + } + .show() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar( + binding.toolbar, + title = getString(R.string.home_backup_and_restore), + subtitle = getString( + if (isBackupMode) R.string.home_backup_config + else R.string.home_restore_config + ), + menuRes = R.menu.menu_backup_restore, + onMenuOptionSelected = this@BackupRestoreFragment::onMenuOptionSelected + ) + + if (isBackupMode) { + importedConfig = ConfigManager.getRawConfig(true) + loadScreenContents() + } else { + binding.mainLayout.isVisible = false + restoreSAFLauncher.launch("application/json") + } + + setEdge2EdgeFlags(binding.root) + } + + @SuppressLint("DefaultLocale") + @Suppress("deprecation") + private fun reloadScreenContents() { + binding.manageApps.subText = getString( + R.string.backup_restore_items_count, + markedForBackup[BRCategory.APP]!!.size, + ) + binding.templateList.subText = getString( + R.string.backup_restore_items_count, + markedForBackup[BRCategory.TEMPLATE]!!.size, + ) + binding.settingsTemplateList.subText = getString( + R.string.backup_restore_items_count, + markedForBackup[BRCategory.SETTINGS_TEMPLATE]!!.size, + ) + } + + private fun loadScreenContents() { + binding.mainLayout.isVisible = true + + markedForBackup[BRCategory.APP]!!.addAll(importedConfig.scope.keys) + markedForBackup[BRCategory.TEMPLATE]!!.addAll(importedConfig.templates.keys) + markedForBackup[BRCategory.SETTINGS_TEMPLATE]!!.addAll(importedConfig.settingsTemplates.keys) + + with(binding.manageApps) { + text = getString(R.string.backup_restore_apps) + setOnClickListener { + showDialogToSelect(BRCategory.APP) + } + } + + with(binding.templateList) { + text = getString(R.string.backup_restore_templates) + setOnClickListener { + showDialogToSelect(BRCategory.TEMPLATE) + } + } + + with(binding.settingsTemplateList) { + text = getString(R.string.backup_restore_settings_templates) + setOnClickListener { + showDialogToSelect(BRCategory.SETTINGS_TEMPLATE) + } + } + + with(binding.switchSettings) { + isChecked = importedConfig.configVersion != CONFIG_VERSION_NO_SETTINGS + isEnabled = isChecked + } + + with(binding.switchOverwriteApps) { + isVisible = !isBackupMode + isChecked = true + + setOnCheckedChangeListener { _, value -> + setText( + if (value) { + R.string.settings_overwrite + } else { + R.string.backup_restore_append + } + ) + } + } + + with(binding.switchOverwriteTemplates) { + isVisible = !isBackupMode + isChecked = true + + setOnCheckedChangeListener { _, value -> + setText( + if (value) { + R.string.settings_overwrite + } else { + R.string.backup_restore_append + } + ) + } + } + + with(binding.switchOverwriteSettingsTemplates) { + isVisible = !isBackupMode + isChecked = true + + setOnCheckedChangeListener { _, value -> + setText( + if (value) { + R.string.settings_overwrite + } else { + R.string.backup_restore_append + } + ) + } + } + + reloadScreenContents() + } + + private fun showDialogToSelect(category: BRCategory) { + val items = when (category) { + BRCategory.APP -> importedConfig.scope.keys + BRCategory.TEMPLATE -> importedConfig.templates.keys + BRCategory.SETTINGS_TEMPLATE -> importedConfig.settingsTemplates.keys + } + + val labels = when (category) { + BRCategory.APP -> items.map { loadAppLabel(it) } + BRCategory.TEMPLATE, BRCategory.SETTINGS_TEMPLATE -> items + }.toTypedArray() + + val checked = items.map { + markedForBackup[category]!!.contains(it) + }.toBooleanArray() + + val title = when (category) { + BRCategory.APP -> getString(R.string.title_app_manage) + BRCategory.TEMPLATE, BRCategory.SETTINGS_TEMPLATE -> getString(R.string.title_template_manage) + } + + val dialog = MaterialAlertDialogBuilder(requireContext()) + .setTitle(title) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + val category = markedForBackup[category] + category!!.clear() + category.addAll(items.mapIndexedNotNull { i, name -> + if (checked[i]) name else null + }) + + reloadScreenContents() + } + + if (items.isNotEmpty()) { + dialog.setMultiChoiceItems(labels, checked) { _, i, value -> + checked[i] = value + } + } else { + val emptyView = LayoutListEmptyBinding.inflate(layoutInflater) + emptyView.root.isVisible = true + emptyView.listEmptyIcon.setImageResource(R.drawable.sentiment_very_dissatisfied_24px) + emptyView.listEmptyText.isVisible = false + dialog.setView(emptyView.root) + } + + dialog.show() + } + + private fun onRestore() = clearNotImportedItems { + if (!overwriteApps || !overwriteTemplates) { + val config = ConfigManager.getRawConfig(false) + + if (!overwriteApps) { + config.scope.map { + importedConfig.scope.putIfAbsent(it.key, it.value) + } + } + + if (!overwriteTemplates) { + config.templates.map { + importedConfig.templates.putIfAbsent(it.key, it.value) + } + } + + if (!overwriteSettingsTemplates) { + config.settingsTemplates.map { + importedConfig.settingsTemplates.putIfAbsent(it.key, it.value) + } + } + } + + if (!includeSettings || importedConfig.configVersion == CONFIG_VERSION_NO_SETTINGS) { + val currentConfig = ConfigManager.getRawConfig(true) + + with(currentConfig.scope) { + clear() + putAll(importedConfig.scope) + } + + with(currentConfig.templates) { + clear() + putAll(importedConfig.templates) + } + + with(currentConfig.settingsTemplates) { + clear() + putAll(importedConfig.settingsTemplates) + } + + ConfigManager.importConfig(currentConfig.toString()) + } else { + ConfigManager.importConfig(importedConfig.toString()) + } + + showToast(android.R.string.ok) + navController.navigateUp() + } + + private fun clearNotImportedItems(onFinish: () -> Unit) { + importedConfig.scope.removeIf { pkg, _ -> + !markedForBackup[BRCategory.APP]!!.contains(pkg) + } + + importedConfig.templates.removeIf { template, _ -> + !markedForBackup[BRCategory.TEMPLATE]!!.contains(template) + } + + importedConfig.settingsTemplates.removeIf { template, _ -> + !markedForBackup[BRCategory.SETTINGS_TEMPLATE]!!.contains(template) + } + + if (isBackupMode) { + cleanRemnantsFromConfig(importedConfig) + } + + if (trimConfig) { + val progressDialog = MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.settings_clear_uninstalled_app_configs) + .setView(R.layout.dialog_loading) + .setCancelable(false) + .create() + + progressDialog.show() + + ConfigManager.clearUninstalledAppConfigs(importedConfig) { + lifecycleScope.launch { + progressDialog.dismiss() + + onFinish() + } + } + } else { + onFinish() + } + } + + @Suppress("unused") + private fun onMenuOptionSelected(item: MenuItem) { + if (isBackupMode) { + val date = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss", Locale.getDefault()).format(Date()) + backupSAFLauncher.launch("HMA-OSS_config_$date.json") + } else { + onRestore() + } + } +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/HomeFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/HomeFragment.kt index 54679487d..eab1b3d9c 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/HomeFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/HomeFragment.kt @@ -9,7 +9,6 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import androidx.activity.result.contract.ActivityResultContracts import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope @@ -25,7 +24,6 @@ import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.attrDrawable import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.getColor import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.homeItemBackgroundColor import icu.nullptr.hidemyapplist.ui.util.ThemeUtils.themeColor -import icu.nullptr.hidemyapplist.ui.util.contentResolver import icu.nullptr.hidemyapplist.ui.util.dp2Px import icu.nullptr.hidemyapplist.ui.util.isTestBuild import icu.nullptr.hidemyapplist.ui.util.navigate @@ -38,10 +36,6 @@ import kotlinx.coroutines.withContext import org.frknkrc44.hma_oss.BuildConfig import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentHomeBinding -import java.io.IOException -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale import kotlin.concurrent.thread /** @@ -52,46 +46,6 @@ import kotlin.concurrent.thread class HomeFragment : Fragment(R.layout.fragment_home) { private val binding by viewBinding(FragmentHomeBinding::bind) - private val backupSAFLauncher = - registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) backup@{ uri -> - if (uri == null) return@backup - ConfigManager.configFile.inputStream().use { input -> - contentResolver.openOutputStream(uri).use { output -> - if (output == null) showToast(R.string.home_export_failed) - else input.copyTo(output) - } - } - showToast(R.string.home_exported) - } - - private val restoreSAFLauncher = - registerForActivityResult(ActivityResultContracts.GetContent()) restore@{ uri -> - if (uri == null) return@restore - runCatching { - val backup = contentResolver - .openInputStream(uri)?.reader().use { it?.readText() } - ?: throw IOException(getString(R.string.home_import_file_damaged)) - ConfigManager.importConfig(backup) - showToast(R.string.home_import_successful) - }.onFailure { - it.printStackTrace() - MaterialAlertDialogBuilder(requireContext()) - .setCancelable(false) - .setTitle(R.string.home_import_failed) - .setMessage(it.message) - .setPositiveButton(android.R.string.ok, null) - .setNegativeButton(R.string.show_crash_log) { _, _ -> - MaterialAlertDialogBuilder(requireActivity()) - .setCancelable(false) - .setTitle(R.string.home_import_failed) - .setMessage(it.stackTraceToString()) - .setPositiveButton(android.R.string.ok, null) - .show() - } - .show() - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { with(binding.toolbar) { setupToolbar( @@ -202,7 +156,9 @@ class HomeFragment : Fragment(R.layout.fragment_home) { .setMessage( getString(R.string.about_how_to_use_description_1) + "\n\n" + - getString(R.string.about_how_to_use_description_2)) + getString(R.string.about_how_to_use_description_2) + + "\n\n" + + getString(R.string.about_how_to_use_description_3)) .setNegativeButton(android.R.string.ok, null) .show() } @@ -248,14 +204,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) { } } - with(binding.navStats) { - text1.text = getString(R.string.title_filter_logs) - icon.setImageResource(R.drawable.outline_cleaning_services_24) - root.setOnClickListener { - navigate(R.id.nav_stats) - } - } - with(binding.navSettings) { text1.text = getString(R.string.title_settings) icon.setImageResource(R.drawable.outline_settings_24) @@ -276,8 +224,10 @@ class HomeFragment : Fragment(R.layout.fragment_home) { if (PrefManager.systemWallpaper) background.alpha = 0xAA setOnClickListener { - val date = SimpleDateFormat("yyyy-MM-dd_HH.mm.ss", Locale.getDefault()).format(Date()) - backupSAFLauncher.launch("HMA-OSS_config_$date.json") + navigate( + R.id.nav_backup_restore, + BackupRestoreFragmentArgs(true).toBundle() + ) } } @@ -285,7 +235,10 @@ class HomeFragment : Fragment(R.layout.fragment_home) { if (PrefManager.systemWallpaper) background.alpha = 0xAA setOnClickListener { - restoreSAFLauncher.launch("application/json") + navigate( + R.id.nav_backup_restore, + BackupRestoreFragmentArgs(false).toBundle() + ) } } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt index a0c872540..babb11cb1 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt @@ -101,12 +101,12 @@ class SettingsTemplateConfFragment : Fragment(R.layout.fragment_template_setting lifecycleScope.launch { viewModel.targetSettingList.collect { - binding.targetApps.text = String.format(getString(R.string.template_setting_count), it.size) + binding.targetApps.text = getString(R.string.template_setting_count, it.size) } } lifecycleScope.launch { viewModel.appliedAppList.collect { - binding.appliedApps.text = String.format(getString(R.string.template_applied_count), it.size) + binding.appliedApps.text = getString(R.string.template_applied_count, it.size) } } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/StatsFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/StatsFragment.kt index 628f7e57c..db6e1328c 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/StatsFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/StatsFragment.kt @@ -4,16 +4,15 @@ import android.os.Bundle import android.view.MenuItem import android.view.View import androidx.fragment.app.Fragment +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import dev.androidbroadcast.vbpd.viewBinding import icu.nullptr.hidemyapplist.common.FilterHolder import icu.nullptr.hidemyapplist.service.ServiceClient -import icu.nullptr.hidemyapplist.ui.util.navController -import icu.nullptr.hidemyapplist.ui.util.setEdge2EdgeFlags -import icu.nullptr.hidemyapplist.ui.util.setupToolbar import icu.nullptr.hidemyapplist.ui.util.showToast +import icu.nullptr.hidemyapplist.util.PackageHelper import kotlinx.coroutines.launch import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentLogsBinding @@ -22,7 +21,19 @@ import org.frknkrc44.hma_oss.ui.adapter.StatAdapter class StatsFragment : Fragment(R.layout.fragment_logs) { private val binding by viewBinding(FragmentLogsBinding::bind) - private val adapter by lazy { StatAdapter() } + private val adapter by lazy { StatAdapter { + lifecycleScope.launch { + PackageHelper.isRefreshing + .flowWithLifecycle(lifecycle) + .collect { isRefreshing -> + if (!isRefreshing && it.wasRefreshing) { + it.wasRefreshing = false + + updateLogs() + } + } + } + } } private var statCache: String? = null private fun updateLogs() { @@ -38,8 +49,8 @@ class StatsFragment : Fragment(R.layout.fragment_logs) { fun getTotalCount(key: String) = stats.filterCounts[key]!!.totalCount val countsKeys = stats.filterCounts.keys.sortedWith { key1, key2 -> - if (getTotalCount(key1) > getTotalCount(key2)) -1 else 1 - } + getTotalCount(key1).compareTo(getTotalCount(key2)) + }.asReversed() for (key in countsKeys) { adapter.addOrUpdateEntry( @@ -53,7 +64,7 @@ class StatsFragment : Fragment(R.layout.fragment_logs) { } } - private fun onMenuOptionSelected(item: MenuItem) { + fun onMenuOptionSelected(item: MenuItem) { when (item.itemId) { R.id.menu_refresh -> updateLogs() R.id.menu_delete -> { @@ -66,23 +77,10 @@ class StatsFragment : Fragment(R.layout.fragment_logs) { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - with(binding.toolbar) { - setupToolbar( - toolbar = this, - title = getString(R.string.title_filter_logs), - menuRes = R.menu.menu_stats, - onMenuOptionSelected = this@StatsFragment::onMenuOptionSelected, - ) - setNavigationIcon(R.drawable.baseline_arrow_back_24) - setNavigationOnClickListener { navController.popBackStack() } - } - binding.list.layoutManager = LinearLayoutManager(context) binding.list.adapter = adapter binding.list.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) - setEdge2EdgeFlags(binding.root) - updateLogs() } } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/TabbedLogsFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/TabbedLogsFragment.kt new file mode 100644 index 000000000..4bb3b5525 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/TabbedLogsFragment.kt @@ -0,0 +1,101 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.tabs.TabLayoutMediator +import dev.androidbroadcast.vbpd.viewBinding +import icu.nullptr.hidemyapplist.service.PrefManager +import icu.nullptr.hidemyapplist.ui.fragment.LogsFragment +import icu.nullptr.hidemyapplist.ui.util.navController +import icu.nullptr.hidemyapplist.ui.util.setEdge2EdgeFlags +import icu.nullptr.hidemyapplist.ui.util.setupToolbar +import org.frknkrc44.hma_oss.R +import org.frknkrc44.hma_oss.databinding.FragmentTabbedLogsBinding + +class TabbedLogsFragment : Fragment() { + private val binding by viewBinding(FragmentTabbedLogsBinding::bind) + + private val tabsList by lazy { + listOf( + getString(R.string.title_logs), + getString(R.string.title_filter_logs), + ) + } + + private val logsFragment by lazy { LogsFragment(binding.loadingIndicator) } + private val statsFragment by lazy { StatsFragment() } + + private val pagerAdapter by lazy { + object : FragmentStateAdapter(parentFragmentManager, lifecycle) { + override fun createFragment(index: Int): Fragment { + return when (index) { + 0 -> logsFragment + 1 -> statsFragment + else -> throw UnsupportedOperationException("Invalid tab index: $index") + } + } + + override fun getItemCount() = tabsList.size + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_tabbed_logs, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + with(binding.toolbar) { + setNavigationIcon(R.drawable.baseline_arrow_back_24) + setNavigationOnClickListener { navController.popBackStack() } + } + + with(binding.viewPager) { + adapter = pagerAdapter + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + when (position) { + 0 -> { + setupToolbar( + binding.toolbar, + title = getString(R.string.title_logs), + menuRes = R.menu.menu_logs, + onMenuOptionSelected = logsFragment::onMenuOptionSelected, + ) + + with(binding.toolbar.menu) { + when (PrefManager.logFilter_level) { + 0 -> findItem(R.id.menu_filter_debug).isChecked = true + 1 -> findItem(R.id.menu_filter_info).isChecked = true + 2 -> findItem(R.id.menu_filter_warn).isChecked = true + 3 -> findItem(R.id.menu_filter_error).isChecked = true + } + findItem(R.id.menu_reverse_order).isChecked = PrefManager.logFilter_reverseOrder + } + } + 1 -> { + setupToolbar( + binding.toolbar, + title = getString(R.string.title_filter_logs), + menuRes = R.menu.menu_stats, + onMenuOptionSelected = statsFragment::onMenuOptionSelected, + ) + } + } + } + }) + } + + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = tabsList[position] + }.attach() + + setEdge2EdgeFlags(binding.root) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_backup_restore.xml b/app/src/main/res/layout/fragment_backup_restore.xml new file mode 100644 index 000000000..a1200695d --- /dev/null +++ b/app/src/main/res/layout/fragment_backup_restore.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 9ea454b65..b87f4576d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -67,10 +67,6 @@ android:id="@+id/nav_logs" layout="@layout/view_home_item" /> - - diff --git a/app/src/main/res/layout/fragment_logs.xml b/app/src/main/res/layout/fragment_logs.xml index 63157f2fc..c5a0ae0bb 100644 --- a/app/src/main/res/layout/fragment_logs.xml +++ b/app/src/main/res/layout/fragment_logs.xml @@ -4,26 +4,6 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_backup_restore.xml b/app/src/main/res/menu/menu_backup_restore.xml new file mode 100644 index 000000000..e710425c1 --- /dev/null +++ b/app/src/main/res/menu/menu_backup_restore.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/home_nav_graph.xml b/app/src/main/res/navigation/home_nav_graph.xml index 4c37135de..460a5bdf6 100644 --- a/app/src/main/res/navigation/home_nav_graph.xml +++ b/app/src/main/res/navigation/home_nav_graph.xml @@ -11,14 +11,9 @@ - - + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 641623c8b..468cca1b4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,6 +61,15 @@ New update available: %s Update: %s + + Backup settings + Restore settings + App configs + Templates + Settings templates + Append + %d items + Show system apps Sorting @@ -177,7 +186,7 @@ Detail log for debugging Logger buffer sizes Hide module icon in launcher - You can still open the app in Xposed manager + You can still open the app in root manager Bypass risky package warning Note that the devs are not responsible for risky package related issues before or after you enabled this feature Disable Activity launch protection @@ -190,6 +199,8 @@ Don\'t enable this option if you see the package list in this app correctly. Launcher icon Error-only log + Disable functions + You can disable some of the functions when they conflict with other modules Enable Internet connection This app will lose all of Internet-related functionality when you disabled this option. @@ -223,18 +234,21 @@ - Although It is incorrect to detect specific app installation, yet not every app using root provides random package name support. In this case, detected apps that use root (such as Fake Location and Storage Isolation) is equal to detected root itself.\n\nAt the same time, some smart apps use various loopholes to acquire your app list, so that it can draw a persona for you.\n\nThis module provides some methods to test whether you have already hidden your applist nicely. Also, it can work as an Xposed module to hide some apps or reject app list requests to protect your privacy. + Although It is incorrect to detect specific app installation, yet not every app using root provides random package name support. In this case, detected apps that use root (such as Fake Location and Storage Isolation) is equal to detected root itself.\n\nAt the same time, some smart apps use various loopholes to acquire your app list, so that it can draw a persona for you.\n\nThis module provides some methods to test whether you have already hidden your applist nicely. Also, it can work as an Zygisk module to hide some apps or reject app list requests to protect your privacy. The original HMA project has become closed-source and their latest code didn\'t contain some of the latest changes, so I created this fork to continue it as open-source again. How to use this module - You can create templates in \"Manage templates\".\nThen apply the templates to apps in \"Manage apps\". (You can also select extra apps for targets.)\nYou should and ONLY should check \"System Framework\" in Xposed module scope / whitelist. + You can create templates in \"Manage templates\".\nThen apply the templates to apps in \"Manage apps\". (You can also select extra apps for targets.) Config modification effective in real time. + + This application acts like a manager of the Zygisk module from now, so you need a separate Zygisk module to make this manager work. + Translator Developer Support and feedback diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 1a2522469..241cae0c7 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -81,6 +81,12 @@ app:singleLineTitle="false" android:title="@string/settings_disable_activity_launch_protection" android:summary="@string/settings_disable_activity_launch_protection_summary" /> + MutableMap.removeIf(predicate: (K, V) -> Boolean) { + this.filter { (key, value) -> predicate(key, value) }.forEach { this.remove(it.key) } + } + + inline fun MutableMap.removeIfWithCount(predicate: (K, V) -> Boolean): Int { + return this.filter { (key, value) -> predicate(key, value) }.count { this.remove(it.key) != null } + } + + inline fun Array<*>.firstWithType(): T { + return this.first { it is T } as T + } + + inline fun Array<*>.firstOrNullWithType(): T? { + return this.firstOrNull { it is T } as? T + } + + inline fun Array<*>.lastWithType(): T { + return this.last { it is T } as T + } + + inline fun Array<*>.lastOrNullWithType(): T? { + return this.lastOrNull { it is T } as? T + } +} diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt index c245934b8..d7f4df87b 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt @@ -30,6 +30,8 @@ object Constants { const val PARCEL_TYPE_LOG = 0 const val PARCEL_TYPE_CONFIG = 1 + const val CONFIG_VERSION_NO_SETTINGS = -100 + /** * Defines the GID for the group that allows write access to the internal media storage. */ diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt index faf72fd93..15a619c83 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt @@ -1,6 +1,5 @@ package icu.nullptr.hidemyapplist.common -import icu.nullptr.hidemyapplist.common.Constants.ENABLE_INTERNET_UNKNOWN import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -74,6 +73,11 @@ data class JsonConfig( val templates: MutableMap = mutableMapOf(), val settingsTemplates: MutableMap = mutableMapOf(), + /** + * A list of disabled hooks, checked while the module is loading + */ + val disabledHooks: MutableList = mutableListOf(), + /** * A package name and config pair to keep per-app configs */ @@ -202,6 +206,19 @@ data class JsonConfig( } } + @Serializable + data class HookItem( + val className: String, + val methodName: String, + val argumentCount: Int, + ) { + override fun toString() = encoder.encodeToString(this) + + companion object { + fun parse(json: String) = encoder.decodeFromString(json) + } + } + companion object { fun parse(json: String) = encoder.decodeFromString(json) diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt index 0010a98d5..b77b0541c 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt @@ -6,23 +6,22 @@ import android.content.pm.PackageInfo import android.content.pm.ResolveInfo import android.os.Binder import android.os.Build -import java.util.Random +import icu.nullptr.hidemyapplist.common.CollectionUtils.removeIf import java.util.zip.ZipFile object Utils { - 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() + fun generateRandomString(length: Int, allowedChars: List): String { + return (0 until length) + .map { allowedChars.random() } + .joinToString("") } + fun generateRandomHex(length: Int) = generateRandomString( + length, + ('a' .. 'f') + ('0' .. '9'), + ) + fun binderLocalScope(block: () -> T): T { val identity = Binder.clearCallingIdentity() val result = block() @@ -72,13 +71,6 @@ object Utils { return targets.any { source.contains(it) } } - fun generateRandomHex(length: Int): String { - val allowedChars = ('a'..'f') + ('0'..'9') - return (1..length) - .map { allowedChars.random() } - .joinToString("") - } - fun getPackageNameFromResolveInfo(resolveInfo: ResolveInfo): String { return resolveInfo.resolvePackageName ?: resolveInfo.activityInfo?.packageName ?: @@ -101,4 +93,18 @@ object Utils { return false } } + + fun cleanRemnantsFromConfig(config: JsonConfig) { + // STEP 1: Remove empty app and settings templates + config.templates.removeIf { _, template -> template.appList.isEmpty() } + config.settingsTemplates.removeIf { _, template -> template.settingsList.isEmpty() } + + // STEP 2: Remove mismatching items + for (app in config.scope.values) { + app.applyTemplates.removeIf { !config.templates.containsKey(it) } + app.applyPresets.removeIf { it !in AppPresets.instance.presetNames } + app.applySettingTemplates.removeIf { !config.settingsTemplates.containsKey(it) } + app.applySettingsPresets.removeIf { it !in SettingsPresets.instance.presetNames } + } + } } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt index 55b710a27..a025c1de3 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt @@ -34,6 +34,7 @@ class DetectorAppsPreset : BasePreset(NAME) { "com.atominvention.rootchecker", "com.joeykrim.rootcheck", "com.studio.duckdetector", + "com.eltavine.duckdetector", "com.chuqniudetector", "com.chunqiudetector", "com.longz.detector", diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/RootAppsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/RootAppsPreset.kt index 8f9e6df36..1e249c987 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/RootAppsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/RootAppsPreset.kt @@ -9,7 +9,16 @@ class RootAppsPreset(private val appPresets: AppPresets) : BasePreset(NAME) { companion object { const val NAME = "root_apps" const val ACCESS_SUPERUSER_PERM = "\u0000a\u0000n\u0000d\u0000r\u0000o\u0000i\u0000d\u0000.\u0000p\u0000e\u0000r\u0000m\u0000i\u0000s\u0000s\u0000i\u0000o\u0000n\u0000.\u0000A\u0000C\u0000C\u0000E\u0000S\u0000S\u0000_\u0000S\u0000U\u0000P\u0000E\u0000R\u0000U\u0000S\u0000E\u0000R" - const val MOZILLA_WHITELIST = "\u0000o\u0000r\u0000g\u0000.\u0000m\u0000o\u0000z\u0000i\u0000l\u0000l\u0000a\u0000.\u0000g\u0000e\u0000c\u0000k\u0000o" + val WHITELISTS = arrayOf( + // Whitelist the Mozilla apps (why a browser app has ACCESS_SUPERUSER?) + "\u0000o\u0000r\u0000g\u0000.\u0000m\u0000o\u0000z\u0000i\u0000l\u0000l\u0000a\u0000.\u0000g\u0000e\u0000c\u0000k\u0000o", + + // Whitelist the Chinese apps (usually games) + "\u0000M\u0000E\u0000I\u0000Z\u0000U\u0000P\u0000U\u0000S\u0000H", + "\u0000h\u0000k\u0000.\u0000a\u0000l\u0000i\u0000p\u0000a\u0000y\u0000.\u0000w\u0000a\u0000l\u0000l\u0000e\u0000t", + "\u0000c\u0000o\u0000m\u0000.\u0000t\u0000e\u0000n\u0000c\u0000e\u0000n\u0000t\u0000.\u0000m\u0000m", + "\u0000c\u0000o\u0000m\u0000.\u0000h\u0000e\u0000y\u0000t\u0000a\u0000p\u0000.", + ) } override val exactPackageNames = setOf( @@ -105,7 +114,7 @@ class RootAppsPreset(private val appPresets: AppPresets) : BasePreset(NAME) { override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { val packageName = appInfo.packageName - // Some of detectors trying to abuse the ACCESS_SUPERUSER permission + // Some of the detectors trying to abuse the ACCESS_SUPERUSER permission if (appPresets.getPresetByName(DetectorAppsPreset.NAME)?.containsPackage(packageName) ?: false) { return false } @@ -171,10 +180,14 @@ class RootAppsPreset(private val appPresets: AppPresets) : BasePreset(NAME) { } return checkSplitPackages(appInfo) { key, zipFile -> + if (findAppsFromLibs(zipFile, libNames) || findAppsFromAssets(zipFile, assetNames)) { + return@checkSplitPackages true + } + val manifestStr = appPresets.readManifest(key, zipFile) - // Whitelist the Mozilla apps (why a browser app has ACCESS_SUPERUSER?) - if (manifestStr.contains(MOZILLA_WHITELIST)) { + // Check for whitelists + if (Utils.containsMultiple(manifestStr, *WHITELISTS)) { return@checkSplitPackages false } @@ -185,10 +198,6 @@ class RootAppsPreset(private val appPresets: AppPresets) : BasePreset(NAME) { return@checkSplitPackages true } - if (findAppsFromLibs(zipFile, libNames) || findAppsFromAssets(zipFile, assetNames)) { - return@checkSplitPackages true - } - return@checkSplitPackages false } } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/XposedModulesPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/XposedModulesPreset.kt index 34bdc5b3f..00b9c6fb1 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/XposedModulesPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/XposedModulesPreset.kt @@ -2,13 +2,16 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo import icu.nullptr.hidemyapplist.common.Utils.checkSplitPackages +import org.frknkrc44.hma_oss.common.BuildConfig class XposedModulesPreset : BasePreset(NAME) { companion object { const val NAME = "xposed" } - override val exactPackageNames = setOf() + override val exactPackageNames = setOf( + BuildConfig.APP_PACKAGE_NAME, + ) override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { return checkSplitPackages(appInfo) { _, zipFile -> diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7c79a72b4..4b334485c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,15 @@ [versions] -#noinspection AndroidGradlePluginVersion +#noinspection GradleDependency,AndroidGradlePluginVersion agp = "8.13.2" -kotlin = "2.3.0" -material = "1.13.0" +kotlin = "2.3.21" +material = "1.14.0" hidden-api = "4.4.0" -androidx-navigation = "2.9.7" +androidx-navigation = "2.9.8" vbpd = "2.0.4" +r8_annotations = "v1.0.1" +androidvmtools = "86f3e1e" +zygoteloader = "bf5078e182" +coil = "3.4.0" [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } @@ -13,26 +17,28 @@ agp-lib = { id = "com.android.library", version.ref = "agp" } kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } nav-safeargs-kotlin = { id = "androidx.navigation.safeargs.kotlin", version.ref = "androidx-navigation" } -autoresconfig = { id = "dev.rikka.tools.autoresconfig", version = "1.2.2" } refine = { id = "dev.rikka.tools.refine", version = "4.4.0" } +# do not update until https://github.com/RikkaApps/MaterialThemeBuilder/pull/13 merged materialthemebuilder = { id = "dev.rikka.tools.materialthemebuilder", version = "1.5.1" } +com-github-aerathstuff-zygoteloader = { id = "com.github.aerath-stuff.ZygoteLoader", version.ref = "zygoteloader" } [libraries] androidx-appcompat-appcompat = { group = "androidx.appcompat", name = "appcompat", version = "1.7.1" } -androidx-annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version = "1.9.1" } +androidx-annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version = "1.10.0" } androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "androidx-navigation" } androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "androidx-navigation" } androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version = "1.2.1" } androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.2.0" } -com-github-bumptech-glide = { module = "com.github.bumptech.glide:glide", version = "5.0.5" } +com-android-tools-build-apksig = { module = "com.android.tools.build:apksig", version.ref = "agp" } dev-androidbroadcast-vbpd = { module = "dev.androidbroadcast.vbpd:vbpd", version.ref = "vbpd" } dev-androidbroadcast-vbpd-reflection = { module = "dev.androidbroadcast.vbpd:vbpd-reflection", version.ref = "vbpd" } -#noinspection NewerVersionAvailable -com-github-kyuubiran-ezxhelper = { module = "com.github.kyuubiran:EzXHelper", version = "1.0.3" } com-github-topjohnwu-libsu-core = { module = "com.github.topjohnwu.libsu:core", version = "6.0.0" } -de-robv-android-xposed-api = { module = "de.robv.android.xposed:api", version = "82" } dev-rikka-hidden-compat = { module = "dev.rikka.hidden:compat", version.ref = "hidden-api" } dev-rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hidden-api" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.10.0" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.11.0" } material = { module = "com.google.android.material:material", version.ref = "material" } me-zhanghai-android-appiconloader = { module = "me.zhanghai.android.appiconloader:appiconloader", version = "1.5.0" } +io-github-vova7878-androidvmtools = { module = "com.github.aerath-stuff:AndroidVMTools", version.ref = "androidvmtools" } +io-github-vova7878-r8annotations = { module = "io.github.vova7878:R8Annotations", version.ref = "r8_annotations" } +io-coilkt-coil3-coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } +io-coilkt-coil3-coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b1c..37f78a6af 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle.kts b/settings.gradle.kts index c775091d7..cd26b3deb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,6 +5,8 @@ pluginManagement { gradlePluginPortal() google() mavenCentral() + mavenLocal() + maven("https://jitpack.io") maven("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") maven("https://maven.aliyun.com/repository/public") } @@ -15,10 +17,10 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + mavenLocal() + maven("https://jitpack.io") maven("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") maven("https://maven.aliyun.com/repository/public") - maven("https://jitpack.io") - maven("https://api.xposed.info/") } } @@ -27,5 +29,5 @@ rootProject.name = "HMA-OSS" include( ":app", ":common", - ":xposed" + ":zygote", ) diff --git a/xposed/build.gradle.kts b/xposed/build.gradle.kts deleted file mode 100644 index 0a2d5701a..000000000 --- a/xposed/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - alias(libs.plugins.agp.lib) - alias(libs.plugins.refine) - alias(libs.plugins.kotlin) -} - -val appPackageName: String by rootProject.extra - -android { - namespace = "$appPackageName.xposed" - - buildFeatures { - buildConfig = false - } -} - -kotlin { - jvmToolchain(21) -} - -dependencies { - implementation(projects.common) - - implementation(libs.androidx.annotation.jvm) - implementation(libs.com.github.kyuubiran.ezxhelper) - implementation(libs.dev.rikka.hidden.compat) - compileOnly(libs.de.robv.android.xposed.api) - compileOnly(libs.dev.rikka.hidden.stub) -} diff --git a/xposed/proguard-rules.pro b/xposed/proguard-rules.pro deleted file mode 100644 index fc2c1b4c9..000000000 --- a/xposed/proguard-rules.pro +++ /dev/null @@ -1,24 +0,0 @@ --keep class com.github.kyuubiran.ezxhelper.utils.** { *; } --keep class icu.nullptr.hidemyapplist.xposed.XposedEntry { *; } --dontwarn java.lang.invoke.StringConcatFactory --dontwarn android.content.res.XModuleResources --dontwarn android.content.res.XResources --dontwarn de.robv.android.xposed.IXposedHookLoadPackage --dontwarn de.robv.android.xposed.IXposedHookZygoteInit$StartupParam --dontwarn de.robv.android.xposed.IXposedHookZygoteInit --dontwarn de.robv.android.xposed.XC_MethodHook$MethodHookParam --dontwarn de.robv.android.xposed.XC_MethodHook$Unhook --dontwarn de.robv.android.xposed.XC_MethodHook --dontwarn de.robv.android.xposed.XC_MethodReplacement --dontwarn de.robv.android.xposed.XposedBridge --dontwarn de.robv.android.xposed.XposedHelpers --dontwarn de.robv.android.xposed.callbacks.XC_LoadPackage$LoadPackageParam --dontwarn org.bouncycastle.jsse.BCSSLParameters --dontwarn org.bouncycastle.jsse.BCSSLSocket --dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider --dontwarn org.conscrypt.Conscrypt$Version --dontwarn org.conscrypt.Conscrypt --dontwarn org.conscrypt.ConscryptHostnameVerifier --dontwarn org.openjsse.javax.net.ssl.SSLParameters --dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE diff --git a/xposed/src/main/assets/xposed_init b/xposed/src/main/assets/xposed_init deleted file mode 100644 index 013e21419..000000000 --- a/xposed/src/main/assets/xposed_init +++ /dev/null @@ -1 +0,0 @@ -icu.nullptr.hidemyapplist.xposed.XposedEntry diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt deleted file mode 100644 index 34a4fc493..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt +++ /dev/null @@ -1,38 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed - -import android.app.ActivityThread -import android.os.Binder -import android.os.Build -import com.github.kyuubiran.ezxhelper.utils.findField -import de.robv.android.xposed.XposedHelpers.callMethod -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.common.Utils - -object Utils4Xposed { - fun getPackageNameFromPackageSettings(packageSettings: Any?): String? { - if (packageSettings == null) return null - - return try { - callMethod(packageSettings, "getPackageName") as String? - } catch (_: Throwable) { - runCatching { - findField(packageSettings::class.java, true) { - name == if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) "mName" else "name" - }.get(packageSettings) as? String - }.getOrNull() - } - } - - fun getPackageManager() = ActivityThread.currentActivityThread().application.packageManager!! - - fun getCallingApps(service: HMAService): Array { - return getCallingApps(service, Binder.getCallingUid()) - } - - fun getCallingApps(service: HMAService, callingUid: Int): Array { - if (callingUid == Constants.UID_SYSTEM) return arrayOf() - return Utils.binderLocalScope { - service.pms.getPackagesForUid(callingUid) - } ?: arrayOf() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt deleted file mode 100644 index cb8fab011..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt +++ /dev/null @@ -1,71 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed - -import android.content.pm.IPackageManager -import com.github.kyuubiran.ezxhelper.init.EzXHelperInit -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.IXposedHookLoadPackage -import de.robv.android.xposed.IXposedHookZygoteInit -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.callbacks.XC_LoadPackage -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import kotlin.concurrent.thread - -private const val TAG = "HMA-XposedEntry" - -@Suppress("unused") -class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { - var targetsLeft = mutableListOf("package", "package_native") - var targetStorage = mutableMapOf() - - override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { - EzXHelperInit.initZygote(startupParam) - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - if (lpparam.packageName == Constants.ANDROID_PACKAGE_NAME) { - EzXHelperInit.initHandleLoadPackage(lpparam) - logI(TAG) { "Hook entry" } - - var serviceManagerHook: XC_MethodHook.Unhook? = null - serviceManagerHook = findMethod("android.os.ServiceManager") { - name == "addService" - }.hookBefore { param -> - val name = param.args[0] as String - if (targetsLeft.contains(name)) { - when (name) { - "package", "package_native" -> { - targetStorage[name] = param.args[1] - targetsLeft.remove(name) - } - else -> { - // skip if there is no package_native available - if (targetStorage.containsKey("package")) { - targetsLeft.remove("package_native") - } - } - } - } - - if (targetsLeft.isEmpty()) { - serviceManagerHook?.unhook() - val pms = targetStorage["package"] as IPackageManager - val pmn = targetStorage["package_native"] - logD(TAG) { "Got pms: $pms, $pmn" } - thread { - runCatching { - UserService.register(pms, pmn) - targetStorage.clear() - logI(TAG) { "User service started" } - }.onFailure { - logE(TAG, it) { "System service crashed" } - } - } - } - } - } - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt deleted file mode 100644 index cd169fa84..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt +++ /dev/null @@ -1,68 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.accessibilityservice.AccessibilityServiceInfo -import android.content.pm.ParceledListSlice -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import icu.nullptr.hidemyapplist.common.settings_presets.AccessibilityPreset -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed -import icu.nullptr.hidemyapplist.xposed.XposedConstants.ACCESSIBILITY_SERVICE_CLASS -import java.lang.reflect.Method - -// Big credits: https://github.com/Nitsuya/DoNotTryAccessibility/blob/main/app/src/main/java/io/github/nitsuya/donottryaccessibility/hook/AndroidFrameworkHooker.kt -class AccessibilityHook(private val service: HMAService) : IFrameworkHook { - companion object { - private const val TAG = "AccessibilityHook" - } - - private val hookList = mutableSetOf() - - override fun load() { - logI(TAG) { "Load hook" } - - hookList += findMethod(ACCESSIBILITY_SERVICE_CLASS) { - name == "getEnabledAccessibilityServiceList" - }.hookBefore { param -> - val callingApps = Utils4Xposed.getCallingApps(service) - if (callingApps.isEmpty()) return@hookBefore - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - val returnedList = java.util.ArrayList() - - logD(TAG) { "@${param.method.name} returned empty list for ${callingApps.contentToString()}" } - - val returnType = (param.method as Method).returnType - param.result = if ("Parcel" in returnType.javaClass.simpleName) { - ParceledListSlice(returnedList) - } else { - returnedList - } - } - } - - hookList += findMethod(ACCESSIBILITY_SERVICE_CLASS) { - name == "addClient" - }.hookBefore { param -> - val callingApps = Utils4Xposed.getCallingApps(service) - if (callingApps.isEmpty()) return@hookBefore - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - param.result = 0L - } - } - } - - private fun callerIsSpoofed(caller: String) = - service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME) - - override fun unload() { - hookList.forEach(XC_MethodHook.Unhook::unhook) - hookList.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ActivityHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ActivityHook.kt deleted file mode 100644 index 04ca6af8d..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ActivityHook.kt +++ /dev/null @@ -1,156 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.Intent -import android.content.pm.ResolveInfo -import android.os.Build -import com.github.kyuubiran.ezxhelper.init.InitFields -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookAfter -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XposedHelpers.findClass -import de.robv.android.xposed.XposedHelpers.getObjectField -import de.robv.android.xposed.XposedHelpers.getStaticIntField -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.common.OSUtils -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Logcat.logV -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed -import icu.nullptr.hidemyapplist.xposed.XposedConstants.ACTIVITY_STACK_SUPERVISOR_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.ACTIVITY_STARTER_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.ACTIVITY_TASK_SUPERVISOR_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.COMPUTER_ENGINE_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PACKAGE_MANAGER_SERVICE_CLASS - -class ActivityHook(private val service: HMAService) : IFrameworkHook { - companion object { - private const val TAG = "ActivityHook" - private val fakeReturnCode by lazy { - getStaticIntField( - findClass( - "android.app.ActivityManager", - InitFields.ezXClassLoader - ), - "START_CLASS_NOT_FOUND" - ) - } - } - - private val hooks = mutableListOf() - - override fun load() { - logI(TAG) { "Load hook" } - - hooks += findMethod(ACTIVITY_STARTER_CLASS) { - name == "execute" - }.hookBefore { param -> - runCatching { - val request = getObjectField(param.thisObject, "mRequest") - val caller = getObjectField(request, "callingPackage") as String? - val intent = getObjectField(request, "intent") as Intent? - val targetApp = intent?.component?.packageName - - if (service.shouldHideActivityLaunch(caller, targetApp)) { - logD(TAG) { - "@executeRequest: insecure query from $caller, target: ${intent?.component}" - } - param.result = fakeReturnCode - service.increaseALFilterCount(caller) - } - }.onFailure { - logE(TAG, it) { "Fatal error occurred, ignore hook" } - // unload() - } - } - - findMethodOrNull(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - ACTIVITY_TASK_SUPERVISOR_CLASS - } else { - ACTIVITY_STACK_SUPERVISOR_CLASS - }) { - name == "checkStartAnyActivityPermission" - }?.hookAfter { param -> - var throwable = param.throwable - - while (throwable != null) { - val newTrace = throwable.stackTrace.filter { item -> - !Utils.containsMultiple( - item.className, - "HookBridge", - "LSPHooker", - "LSPosed", - ) - } - - if (newTrace.size != throwable.stackTrace.size) { - throwable.stackTrace = newTrace.toTypedArray() - - val callingUid = param.args.lastOrNull { it is Int } as Int? - - logD(TAG) { "@checkStartAnyActivityPermission: ${throwable.stackTrace.size - newTrace.size} remnants cleared for $callingUid!" } - - service.increaseALFilterCount(callingUid) - } - - throwable = throwable.cause - } - }?.let { - hooks += it - logD(TAG) { "Loaded ${it.hookedMethod.name} hook from ${it.hookedMethod.declaringClass}!" } - } - - if (!OSUtils.isSamsung()) { - hooks += findMethod( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - COMPUTER_ENGINE_CLASS - } else { - PACKAGE_MANAGER_SERVICE_CLASS - }, - findSuper = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU, - ) { - name == "applyPostResolutionFilter" - }.hookBefore { param -> - @Suppress("UNCHECKED_CAST") // I know what I do - val list = param.args.first() as List? - if (list.isNullOrEmpty()) return@hookBefore - - val callingUid = param.args.first { it is Int } as Int - if (callingUid == Constants.UID_SYSTEM) return@hookBefore - - val callingApps = Utils4Xposed.getCallingApps(service, callingUid) - val caller = callingApps.firstOrNull { service.isHookEnabled(it) } - if (caller != null) { - logV(TAG) { "@${param.method.name}: $caller requested a resolve info" } - - val filteredList = list.filter { resolveInfo -> - val targetApp = Utils.getPackageNameFromResolveInfo(resolveInfo) - - logV(TAG) { "@${param.method.name}: Checking $targetApp for $caller" } - - (!service.shouldHideActivityLaunch(caller, targetApp)).apply { - if (!this) { - logD(TAG) { "@${param.method.name}: Filtered $targetApp from $caller" } - } - } - } - - if (filteredList.size != list.size) { - param.args[0] = filteredList.toList() - - service.increasePMFilterCount(caller) - } - } - } - } - } - - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt deleted file mode 100644 index 95427e505..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt +++ /dev/null @@ -1,217 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.os.Build -import android.os.SystemProperties -import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookAfter -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XposedHelpers -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.XposedConstants.STORAGE_MANAGER_SERVICE_CLASS -import org.frknkrc44.hma_oss.common.BuildConfig - -@RequiresApi(Build.VERSION_CODES.R) -class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { - - companion object { - private const val TAG = "AppDataIsolationHook" - private const val APPDATA_ISOLATION_ENABLED = "mAppDataIsolationEnabled" - private const val VOLD_APPDATA_ISOLATION_ENABLED = "mVoldAppDataIsolationEnabled" - private const val FUSE_PROP = "persist.sys.fuse" - } - - private val hooks = mutableListOf() - private var voldHookSkipped = false - - override fun load() { - if (!(service.config.altAppDataIsolation || service.config.altVoldAppDataIsolation)) return - logI(TAG) { "Load hook" } - - findMethodOrNull( - "com.android.server.am.ProcessList" - ) { - name == "startProcess" - }?.hookBefore { param -> - if (service.config.altAppDataIsolation) { - val isEnabled = XposedHelpers.getBooleanField( - param.thisObject, - APPDATA_ISOLATION_ENABLED - ) - - if (!isEnabled) { - XposedHelpers.setBooleanField( - param.thisObject, - APPDATA_ISOLATION_ENABLED, - true - ) - - logI(TAG) { "ProcessList - App data isolation is forced" } - } - } - - if (service.config.altVoldAppDataIsolation && !voldHookSkipped) { - val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) - - if (!fuseEnabled) { - voldHookSkipped = true - logE(TAG) { "ProcessList - FUSE storage is not enabled, skip vold hook" } - } else { - val isolationEnabled = XposedHelpers.getBooleanField( - param.thisObject, - VOLD_APPDATA_ISOLATION_ENABLED - ) - - if (!isolationEnabled) { - XposedHelpers.setBooleanField( - param.thisObject, - VOLD_APPDATA_ISOLATION_ENABLED, - true - ) - - logI(TAG) { "ProcessList - Vold app data isolation is forced" } - } - } - } - }?.let { - hooks += it - } - - findMethodOrNull( - "com.android.server.am.ProcessList" - ) { - name == "needsStorageDataIsolation" - }?.hookAfter { param -> - if (service.config.altVoldAppDataIsolation) { - val app = param.args.find { it.javaClass.simpleName == "ProcessRecord" } - val uid = XposedHelpers.getIntField(app, "uid") - val processName = runCatching { - XposedHelpers.getObjectField(app, "processName") - }.getOrDefault("") - val mountNode = runCatching { - XposedHelpers.getIntField(app, "mMountMode") - }.getOrDefault(0) - val isolated = runCatching { - XposedHelpers.getBooleanField(app, "isolated") - }.getOrDefault(false) - val appZygote = runCatching { - XposedHelpers.getBooleanField(app, "appZygote") - }.getOrDefault(false) - - val apps = Utils.binderLocalScope { - service.pms.getPackagesForUid(uid) - } ?: return@hookAfter - - logD(TAG) { - "@needsStorageDataIsolation $uid and ${apps.contentToString()} - $processName value without override: ${param.result}, mount node: $mountNode, isolated: $isolated, appZygote: $appZygote" - } - - // Do not isolate this module for safety - if (apps.contains(BuildConfig.APP_PACKAGE_NAME)) { - param.result = false - return@hookAfter - } - - if (apps.any { service.isAppDataIsolationExcluded(it) }) { - param.result = false - } - - if (service.config.skipSystemAppDataIsolation) { - val isSystemApp = service.systemApps.any { apps.contains(it) } - logD(TAG) { - "@needsStorageDataIsolation $uid and ${apps.contentToString()} - isSystemApp: $isSystemApp" - } - - if (isSystemApp) { - param.result = false - return@hookAfter - } - } - } - }?.let { - hooks += it - } - - findMethodOrNull(STORAGE_MANAGER_SERVICE_CLASS) { - name == "onVolumeStateChangedLocked" - }?.hookBefore { param -> - runCatching { - if (service.config.altVoldAppDataIsolation) { - val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) - - if (!fuseEnabled) { - logE(TAG) { "StorageManagerService - FUSE storage is not enabled, disable hooks" } - unload() - return@hookBefore - } - - val isolationEnabled = XposedHelpers.getBooleanField( - param.thisObject, - VOLD_APPDATA_ISOLATION_ENABLED - ) - - if (!isolationEnabled) { - XposedHelpers.setBooleanField( - param.thisObject, - VOLD_APPDATA_ISOLATION_ENABLED, - true - ) - - logI(TAG) { "StorageManagerService - Vold app data isolation is forced" } - } - } - }.onFailure { - logE(TAG, it) { "Fatal error occurred, disable hooks" } - unload() - } - }?.let { - hooks += it - } - - findMethodOrNull(STORAGE_MANAGER_SERVICE_CLASS) { - name == "remountAppStorageDirs" - }?.hookBefore { param -> - if (service.config.altVoldAppDataIsolation && service.config.skipSystemAppDataIsolation) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - val pidPkgMap = param.args[0] as java.util.Map<*, *> - val userId = param.args[1] as Int - - val keysToRemove = mutableSetOf() - - for (entry in pidPkgMap.entrySet()) { - val pid = entry.key - val packageName = entry.value as String - - val apps = Utils.binderLocalScope { - val uid = Utils.getPackageUidCompat(service.pms, packageName, 0L, userId) - service.pms.getPackagesForUid(uid) - } ?: continue - - for (app in apps) { - if (app in service.systemApps || app == BuildConfig.APP_PACKAGE_NAME) { - logD(TAG) { - "@remountAppStorageDirs SYSTEM $pid - $packageName is marked to remove" - } - keysToRemove += pid - break - } - } - } - - keysToRemove.forEach { pidPkgMap.remove(it) } - } - }?.let { - hooks += it - } - } - - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt deleted file mode 100644 index 7368718c8..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt +++ /dev/null @@ -1,172 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.AttributionSource -import android.database.Cursor -import android.database.MatrixCursor -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.provider.Settings -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookAfter -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed -import icu.nullptr.hidemyapplist.xposed.XposedConstants.CONTENT_PROVIDER_TRANSPORT_CLASS - -class ContentProviderHook(private val service: HMAService): IFrameworkHook { - companion object { - private const val TAG = "ContentProviderHook" - private val NV_PAIR = arrayOf("name", "value") - } - - private val hooks = mutableListOf() - - @Suppress("UNCHECKED_CAST") - override fun load() { - hooks += findMethod(CONTENT_PROVIDER_TRANSPORT_CLASS) { - name == "query" - }.hookAfter { param -> - val callingApps = getCallingPackages(param) - - val caller = callingApps.firstOrNull { service.isHookEnabled(it) } - if (caller == null) return@hookAfter - - val uri = param.args[1] as Uri? - val projection = param.args[2] as Array? - val args = param.args[3] as Bundle? - - if (uri?.authority != "settings") return@hookAfter - - val segments = uri.pathSegments - if (segments.isEmpty()) return@hookAfter - - logD(TAG) { "@spoofSettings QUERY in ${callingApps.contentToString()}: $uri, ${projection?.contentToString()}, $args" } - - val database = segments[0] - - if (segments.size >= 2) { - val name = segments[1] - - logD(TAG) { "@spoofSettings QUERY received caller: $caller, database: $database, name: $name" } - - val replacement = service.getSpoofedSetting(caller, name, database) - if (replacement != null) { - logD(TAG) { "@spoofSettings QUERY $name in $database replaced for $caller" } - param.result = MatrixCursor(arrayOf("name", "value"), 1).apply { - addRow(arrayOf(replacement.name, replacement.value)) - } - - service.increaseSettingsFilterCount(caller) - } - } else { - logD(TAG) { "@spoofSettings LIST_QUERY received caller: $caller, database: $database" } - - val result = param.result as Cursor? ?: return@hookAfter - - val columns = mutableMapOf>().apply { - for (i in 0 ..< result.columnCount) { - put(result.getColumnName(i), mutableListOf()) - } - } - - logD(TAG) { "@spoofSetting LIST_QUERY columns: ${columns.keys}" } - - val keyColumn = columns["name"] - val valueColumn = columns["value"] - - if (keyColumn == null || valueColumn == null) { - logD(TAG) { "@spoofSettings LIST_QUERY invalid query: $caller ($keyColumn, $valueColumn)" } - return@hookAfter - } - - while (result.moveToNext()) { - val name = result.getString(columns.keys.indexOf("name")) - keyColumn.add(name) - - val replacement = service.getSpoofedSetting(caller, name, database) - val value = if (replacement != null) { - logD(TAG) { "@spoofSettings QUERY $name in $database replaced for $caller" } - - service.increaseSettingsFilterCount(caller) - - replacement.value - } else { - result.getString(columns.keys.indexOf("value")) - } - - valueColumn.add(value) - - if (columns.keys.size > 2) { - for (otherCol in columns.keys.filter { it !in NV_PAIR }) { - val other = result.getString(columns.keys.indexOf(otherCol)) - - columns[otherCol]!!.add(other) - } - } - } - - param.result = MatrixCursor(columns.keys.toTypedArray(), columns.size).apply { - val size = columns.values.first().size - for (i in 0 ..< size) { - val innerList = mutableListOf() - - columns.values.forEach { colVal -> - innerList.add(colVal[i]) - } - - addRow(innerList) - } - } - } - } - - // Credit: https://github.com/Nitsuya/DoNotTryAccessibility/blob/main/app/src/main/java/io/github/nitsuya/donottryaccessibility/hook/AndroidFrameworkHooker.kt - hooks += findMethod(CONTENT_PROVIDER_TRANSPORT_CLASS) { - name == "call" - }.hookBefore { param -> - val callingApps = getCallingPackages(param) - val caller = callingApps.firstOrNull { service.isHookEnabled(it) } - if (caller == null) return@hookBefore - - val method = param.args[2] as String? - val name = param.args[3] as String? - - logD(TAG) { "@spoofSettings CALL received caller: ${callingApps.contentToString()}, method: $method, name: $name" } - - when (method) { - "GET_global", "GET_secure", "GET_system" -> { - val database = method.substring(method.indexOf('_') + 1) - val replacement = service.getSpoofedSetting(caller, name, database) - if (replacement != null) { - logD(TAG) { "@spoofSettings CALL $name in $database replaced for $caller" } - param.result = Bundle().apply { - putString(Settings.NameValueTable.VALUE, replacement.value) - putInt("_generation_index", -1) - } - - service.increaseSettingsFilterCount(caller) - } - } - } - } - } - - private fun getCallingPackages(param: XC_MethodHook.MethodHookParam) = try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val attrSource = param.args.first() as AttributionSource - arrayOf(attrSource.packageName) - } else { - arrayOf(param.args.first() as String) - } - } catch (_: Throwable) { - Utils4Xposed.getCallingApps(service) - } - - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/IFrameworkHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/IFrameworkHook.kt deleted file mode 100644 index cbea16b2d..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/IFrameworkHook.kt +++ /dev/null @@ -1,8 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -interface IFrameworkHook { - - fun load() - fun unload() - fun onConfigChanged() {} -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ImmHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ImmHook.kt deleted file mode 100644 index bb614aac3..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ImmHook.kt +++ /dev/null @@ -1,190 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.ComponentName -import android.os.Build -import android.provider.Settings -import android.view.inputmethod.InputMethodInfo -import android.view.inputmethod.InputMethodSubtype -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.common.settings_presets.InputMethodPreset -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed -import icu.nullptr.hidemyapplist.xposed.XposedConstants.IMM_SERVICE_CLASS -import java.util.Collections - -class ImmHook(private val service: HMAService) : IFrameworkHook { - companion object { - private const val TAG = "ImmHook" - } - - private val hooks = mutableListOf() - - // TODO: Find a method to get settings activity - fun getFakeInputMethodInfo(packageName: String): InputMethodInfo { - val defaultInputMethod = service.getSpoofedSetting( - packageName, - Settings.Secure.DEFAULT_INPUT_METHOD, - Constants.SETTINGS_SECURE, - ) - - if (defaultInputMethod?.value != null) { - try { - val component = ComponentName.unflattenFromString(defaultInputMethod.value!!)!! - logD(TAG) { "Package component: \"$component\"" } - - val pkgManager = Utils4Xposed.getPackageManager() - val kbdPackage = Utils.binderLocalScope { - pkgManager.getApplicationInfo(component.packageName, 0) - } - - return InputMethodInfo( - component.packageName, - component.className, - kbdPackage.loadLabel(pkgManager), - null, - ) - } catch (e: Throwable) { - logE(TAG, e) { e.message ?: "" } - } - } - - return InputMethodInfo( - "com.google.android.inputmethod.latin", - "com.android.inputmethod.latin.LatinIME", - "Gboard", - null, - ) - } - - override fun load() { - logI(TAG) { "Load hook" } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getCurrentInputMethodInfoAsUser" - }?.hookBefore { param -> - val callingApps = Utils4Xposed.getCallingApps(service) - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - logD(TAG) { "@${param.method.name} spoofed input method for $caller" } - - param.result = getFakeInputMethodInfo(caller) - service.increaseSettingsFilterCount(caller) - } - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - } - - (findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getInputMethodListInternal" - } ?: findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getInputMethodList" && returnType.simpleName != "InputMethodInfoSafeList" - })?.hookBefore { param -> - listHook(param) - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - - (findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getEnabledInputMethodListInternal" - } ?: findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getEnabledInputMethodList" && returnType.simpleName != "InputMethodInfoSafeList" - })?.hookBefore { param -> - listHook(param) - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - - findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getCurrentInputMethodSubtype" - }?.hookBefore { param -> - subtypeHook(param) - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - - findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getLastInputMethodSubtype" - }?.hookBefore { param -> - subtypeHook(param) - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - - (findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getEnabledInputMethodSubtypeListInternal" - } ?: findMethodOrNull(IMM_SERVICE_CLASS) { - name == "getEnabledInputMethodSubtypeList" - })?.hookBefore { param -> - subtypeListHook(param) - }?.let { - logD(TAG) { "@${it.hookedMethod.name} is hooked!" } - hooks += it - } - } - - private fun listHook(param: XC_MethodHook.MethodHookParam) { - val callingApps = if (param.method.name.endsWith("Internal")) { - val callingUid = param.args.last() as Int - Utils4Xposed.getCallingApps(service, callingUid) - } else { - Utils4Xposed.getCallingApps(service) - } - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - logD(TAG) { "@${param.method.name} spoofed input method for $caller" } - - param.result = listOf(getFakeInputMethodInfo(caller)) - service.increaseSettingsFilterCount(caller) - } - } - - private fun subtypeHook(param: XC_MethodHook.MethodHookParam) { - val callingApps = Utils4Xposed.getCallingApps(service) - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - logD(TAG) { "@${param.method.name} spoofed input method subtype for ${callingApps.contentToString()}" } - - // TODO: Find a method to get exact value for spoofed input method - param.result = null - service.increaseSettingsFilterCount(caller) - } - } - - private fun subtypeListHook(param: XC_MethodHook.MethodHookParam) { - val callingApps = Utils4Xposed.getCallingApps(service) - - val caller = callingApps.firstOrNull { callerIsSpoofed(it) } - if (caller != null) { - logD(TAG) { "@${param.method.name} spoofed input method subtype for ${callingApps.contentToString()}" } - - // TODO: Find a method to get exact list for spoofed input method - param.result = Collections.emptyList() - service.increaseSettingsFilterCount(caller) - } - } - - private fun callerIsSpoofed(caller: String) = - service.getEnabledSettingsPresets(caller).contains(InputMethodPreset.NAME) - - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget29.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget29.kt deleted file mode 100644 index d4e459df3..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget29.kt +++ /dev/null @@ -1,36 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getCallingApps -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings - -class PmsHookTarget29(service: HMAService) : PmsHookTargetBase(service) { - - override val TAG = "PmsHookTarget29" - - // not required until SDK 30 - override val fakeSystemPackageInstallSourceInfo = null - override val fakeUserPackageInstallSourceInfo = null - - @Suppress("UNCHECKED_CAST") - override fun load() { - logI(TAG) { "Load hook" } - - hooks += findMethod(service.pms::class.java, findSuper = true) { - name == "filterAppAccessLPr" && parameterCount == 5 - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[1] as Int? }, - { getPackageNameFromPackageSettings(param.args[0]) }, - { getCallingApps(service, it) }, - { param.result = true }, - ) - } - - super.load() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget30.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget30.kt deleted file mode 100644 index 9f71d67eb..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget30.kt +++ /dev/null @@ -1,81 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.os.Binder -import android.os.Build -import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findConstructor -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import com.github.kyuubiran.ezxhelper.utils.paramCount -import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getCallingApps -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings -import icu.nullptr.hidemyapplist.xposed.XposedConstants.APPS_FILTER_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PACKAGE_MANAGER_SERVICE_CLASS - -@RequiresApi(Build.VERSION_CODES.R) -class PmsHookTarget30(service: HMAService) : PmsHookTargetBase(service) { - - override val TAG = "PmsHookTarget30" - - override val fakeSystemPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 4 - }.newInstance( - null, - null, - null, - null, - ) - } - - override val fakeUserPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 4 - }.newInstance( - VENDING_PACKAGE_NAME, - psPackageInfo?.signingInfo, - VENDING_PACKAGE_NAME, - VENDING_PACKAGE_NAME, - ) - } - - override fun load() { - logI(TAG) { "Load hook" } - - findMethodOrNull(PACKAGE_MANAGER_SERVICE_CLASS, findSuper = true) { - name == "getPackageSetting" - }?.hookBefore { param -> - applyPackageHiding( - param.method.name, - { Binder.getCallingUid() }, - { param.args[0] as String? }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - }?.let { - hooks += it - } - - hooks += findMethod(APPS_FILTER_CLASS) { - name == "shouldFilterApplication" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[0] as Int }, - { getPackageNameFromPackageSettings(param.args[2]) }, - { getCallingApps(service, it) }, - { param.result = true }, - ) - } - - super.load() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt deleted file mode 100644 index 14a0faa6b..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt +++ /dev/null @@ -1,95 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.os.Binder -import android.os.Build -import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findConstructor -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import com.github.kyuubiran.ezxhelper.utils.paramCount -import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getCallingApps -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings -import icu.nullptr.hidemyapplist.xposed.XposedConstants.APPS_FILTER_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PMS_COMPUTER_TRACKER_CLASS - -@RequiresApi(Build.VERSION_CODES.S) -class PmsHookTarget31(service: HMAService) : PmsHookTargetBase(service) { - - override val TAG = "PmsHookTarget31" - - override val fakeSystemPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 4 - }.newInstance( - null, - null, - null, - null, - ) - } - - override val fakeUserPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 4 - }.newInstance( - VENDING_PACKAGE_NAME, - psPackageInfo?.signingInfo, - VENDING_PACKAGE_NAME, - VENDING_PACKAGE_NAME, - ) - } - - override fun load() { - logI(TAG) { "Load hook" } - - findMethodOrNull(PMS_COMPUTER_TRACKER_CLASS) { - name == "getPackageSetting" - }?.hookBefore { param -> - applyPackageHiding( - param.method.name, - { Binder.getCallingUid() }, - { param.args[0] as String? }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - }?.let { - hooks += it - } - - findMethodOrNull(PMS_COMPUTER_TRACKER_CLASS) { - name == "getPackageSettingInternal" - }?.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[1] as Int? }, - { param.args[0] as String? }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - }?.let { - hooks += it - } - - hooks += findMethod(APPS_FILTER_CLASS) { - name == "shouldFilterApplication" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[0] as Int? }, - { getPackageNameFromPackageSettings(param.args[2]) }, - { getCallingApps(service, it) }, - { param.result = true }, - ) - } - - super.load() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget33.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget33.kt deleted file mode 100644 index 378b4cfa5..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget33.kt +++ /dev/null @@ -1,78 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.pm.PackageInstaller -import android.os.Build -import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findConstructor -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import com.github.kyuubiran.ezxhelper.utils.paramCount -import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings -import icu.nullptr.hidemyapplist.xposed.XposedConstants.APPS_FILTER_IMPL_CLASS - -@RequiresApi(Build.VERSION_CODES.TIRAMISU) -class PmsHookTarget33(service: HMAService) : PmsHookTargetBase(service) { - - override val TAG = "PmsHookTarget33" - - private val getPackagesForUidMethod by lazy { - findMethod("com.android.server.pm.Computer") { - name == "getPackagesForUid" - } - } - - override val fakeSystemPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 5 - }.newInstance( - null, - null, - null, - null, - PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, - ) - } - - override val fakeUserPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 5 - }.newInstance( - VENDING_PACKAGE_NAME, - psPackageInfo?.signingInfo, - VENDING_PACKAGE_NAME, - VENDING_PACKAGE_NAME, - PackageInstaller.PACKAGE_SOURCE_STORE, - ) - } - - @Suppress("UNCHECKED_CAST") - override fun load() { - logI(TAG) { "Load hook" } - - hooks += findMethod(APPS_FILTER_IMPL_CLASS, findSuper = true) { - name == "shouldFilterApplication" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[1] as Int? }, - { getPackageNameFromPackageSettings(param.args[3]) }, - { - Utils.binderLocalScope { - getPackagesForUidMethod.invoke(param.args[0], it) as Array? - } - }, - { param.result = true }, - ) - } - - super.load() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget34.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget34.kt deleted file mode 100644 index effc38908..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget34.kt +++ /dev/null @@ -1,100 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.pm.PackageInstaller -import android.os.Binder -import android.os.Build -import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findConstructor -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import com.github.kyuubiran.ezxhelper.utils.paramCount -import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getCallingApps -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings -import icu.nullptr.hidemyapplist.xposed.XposedConstants.APPS_FILTER_IMPL_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PACKAGE_MANAGER_SERVICE_CLASS - -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -class PmsHookTarget34(service: HMAService) : PmsHookTargetBase(service) { - - override val TAG = "PmsHookTarget34" - - private val getPackagesForUidMethod by lazy { - findMethod("com.android.server.pm.Computer") { - name == "getPackagesForUid" - } - } - - override val fakeSystemPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 6 - }.newInstance( - null, - null, - null, - null, - null, - PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, - ) - } - - override val fakeUserPackageInstallSourceInfo: Any by lazy { - findConstructor( - "android.content.pm.InstallSourceInfo" - ) { - paramCount == 6 - }.newInstance( - VENDING_PACKAGE_NAME, - psPackageInfo?.signingInfo, - VENDING_PACKAGE_NAME, - VENDING_PACKAGE_NAME, - VENDING_PACKAGE_NAME, - PackageInstaller.PACKAGE_SOURCE_STORE, - ) - } - - @Suppress("UNCHECKED_CAST") - override fun load() { - logI(TAG) { "Load hook" } - - hooks += findMethod(APPS_FILTER_IMPL_CLASS, findSuper = true) { - name == "shouldFilterApplication" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[1] as Int? }, - { getPackageNameFromPackageSettings(param.args[3]) }, - { - Utils.binderLocalScope { - getPackagesForUidMethod.invoke(param.args[0], it) as Array? - } - }, - { param.result = true }, - ) - } - - // AOSP exploit - https://github.com/aosp-mirror/platform_frameworks_base/commit/5bc482bd99ea18fe0b4064d486b29d5ae2d65139 - // Only 14 QPR2+ has this method - findMethodOrNull(PACKAGE_MANAGER_SERVICE_CLASS, findSuper = true) { - name == "getArchivedPackageInternal" - }?.hookBefore { param -> - applyPackageHiding( - param.method.name, - { Binder.getCallingUid() }, - { param.args[0].toString() }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - }?.let { - hooks.add(it) - } - - super.load() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt deleted file mode 100644 index a32f974bb..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt +++ /dev/null @@ -1,321 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.pm.PackageManager -import android.os.Binder -import android.os.Build -import android.os.UserHandle -import android.util.ArrayMap -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookAfter -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XposedHelpers -import de.robv.android.xposed.XposedHelpers.callMethod -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME -import icu.nullptr.hidemyapplist.common.OSUtils -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Logcat.logV -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getCallingApps -import icu.nullptr.hidemyapplist.xposed.Utils4Xposed.getPackageNameFromPackageSettings -import icu.nullptr.hidemyapplist.xposed.XposedConstants.COMPUTER_ENGINE_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PACKAGE_MANAGER_SERVICE_CLASS -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PMS_COMPUTER_TRACKER_CLASS -import java.util.concurrent.atomic.AtomicReference - -abstract class PmsHookTargetBase(protected val service: HMAService) : IFrameworkHook { - - @Suppress("PropertyName") - abstract val TAG: String - - private val androidPkgClazzNames = arrayOf("AndroidPackage", "PackageImpl") - - protected val hooks = mutableListOf() - protected var lastFilteredApp: AtomicReference = AtomicReference(null) - - protected val psPackageInfo by lazy { - try { - Utils.getPackageInfoCompat( - service.pms, - VENDING_PACKAGE_NAME, - PackageManager.GET_SIGNING_CERTIFICATES.toLong(), - 0 - ) - } catch (_: Throwable) { - null - } - } - - abstract val fakeSystemPackageInstallSourceInfo: Any? - abstract val fakeUserPackageInstallSourceInfo: Any? - - override fun load() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - hooks += findMethod(COMPUTER_ENGINE_CLASS) { - name == "getPackageStates" - }.hookAfter { param -> - val callingUid = Binder.getCallingUid() - if (callingUid == Constants.UID_SYSTEM) return@hookAfter - - val callingApps = getCallingApps(service, callingUid) - val caller = callingApps.firstOrNull { service.isHookEnabled(it) } - if (caller != null) { - logD(TAG) { "@getPackageStates: incoming query from $caller" } - - val result = param.result as ArrayMap<*, *> - val markedToRemove = mutableListOf() - - for (pair in result.entries) { - val value = pair.value - val packageName = XposedHelpers.callMethod(value, "getPackageName") as String - if (service.shouldHide(caller, packageName)) { - markedToRemove.add(pair.key) - } - } - - if (markedToRemove.isNotEmpty()) { - val copyResult = ArrayMap(result) - copyResult.removeAll(markedToRemove) - logD(TAG) { "@getPackageStates: removed ${markedToRemove.size} entries from $caller" } - param.result = copyResult - service.increasePMFilterCount(caller) - } - } - } - - // Samsung related fix - if (OSUtils.isSamsung()) { - findMethod(COMPUTER_ENGINE_CLASS) { - name == "generatePackageInfo" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { Binder.getCallingUid() }, - { getPackageNameFromPackageSettings(param.args[0]) }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - } - } - - // Samsung devices can fail to get this hook working, - // but it is okay due to generatePackageInfo hook - findMethodOrNull(COMPUTER_ENGINE_CLASS) { - name == "addPackageHoldingPermissions" - }?.hookBefore { param -> - applyPackageHiding( - param.method.name, - { Binder.getCallingUid() }, - { getPackageNameFromPackageSettings(param.args[1]) }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - }?.let { - logD(TAG) { "CE addPackageHoldingPermissions is hooked!" } - hooks += it - } - - hooks += findMethod(COMPUTER_ENGINE_CLASS) { - name == "isCallerInstallerOfRecord" - }.hookBefore { param -> - val callingUid = param.args.last() as Int - if (callingUid == Constants.UID_SYSTEM) return@hookBefore - - val pkg = param.args.lastOrNull { - it?.javaClass?.simpleName in androidPkgClazzNames - } ?: return@hookBefore - val query = callMethod( - pkg, - if (pkg.javaClass.simpleName == "PackageImpl") { - "getManifestPackageName" - } else { - "getPackageName" - }) as? String ?: return@hookBefore - - val callingApps = getCallingApps(service, callingUid) - val callingHandle = UserHandle.getUserHandleForUid(callingUid) - - for (caller in callingApps) { - when (service.shouldHideInstallationSource(caller, query, callingHandle)) { - Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = callingUid == psPackageInfo?.applicationInfo?.uid - Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = false - else -> continue - } - - service.increaseInstallerFilterCount(caller) - break - } - } - - hooks += findMethod(COMPUTER_ENGINE_CLASS) { - name == "getPackageInfoInternal" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args.firstOrNull { it is Int } as? Int }, - { param.args.firstOrNull { it is String } as? String }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - } - - hooks += findMethod(COMPUTER_ENGINE_CLASS) { - name == "getApplicationInfoInternal" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args.firstOrNull { it is Int } as? Int }, - { param.args.firstOrNull { it is String } as? String }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - } - } else { - val clazzToHook = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PMS_COMPUTER_TRACKER_CLASS - } else { - PACKAGE_MANAGER_SERVICE_CLASS - } - - hooks += findMethod(clazzToHook) { - name == "getPackageInfoInternal" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[3] as Int? }, - { param.args[0] as String? }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - } - - hooks += findMethod(clazzToHook) { - name == "getApplicationInfoInternal" - }.hookBefore { param -> - applyPackageHiding( - param.method.name, - { param.args[2] as Int? }, - { param.args[0] as String? }, - { getCallingApps(service, it) }, - { param.result = null }, - ) - } - } - - if (service.pmn != null) { - findMethodOrNull(service.pmn::class.java, findSuper = true) { - name == "getInstallerForPackage" - }?.hookBefore { param -> - val query = param.args[0] as String? - - val callingUid = Binder.getCallingUid() - if (callingUid == Constants.UID_SYSTEM) return@hookBefore - - val callingApps = getCallingApps(service, callingUid) - val callingHandle = UserHandle.getUserHandleForUid(callingUid) - - for (caller in callingApps) { - when (service.shouldHideInstallationSource(caller, query, callingHandle)) { - Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME - Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = "preload" - else -> continue - } - - service.increaseInstallerFilterCount(caller) - break - } - }?.let { - logD(TAG) { "PMN getInstallerForPackage is hooked!" } - hooks.add(it) - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - findMethodOrNull(service.pms::class.java, findSuper = true) { - name == "getInstallSourceInfo" - }?.hookBefore { param -> - val query = param.args[0] as String? - - val callingUid = Binder.getCallingUid() - if (callingUid == Constants.UID_SYSTEM) return@hookBefore - - val callingApps = getCallingApps(service, callingUid) - val callingHandle = UserHandle.getUserHandleForUid(callingUid) - - for (caller in callingApps) { - when (service.shouldHideInstallationSource(caller, query, callingHandle)) { - Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = fakeUserPackageInstallSourceInfo - Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = fakeSystemPackageInstallSourceInfo - else -> continue - } - - service.increaseInstallerFilterCount(caller) - break - } - }?.let { - hooks.add(it) - } - } - - hooks += findMethod(service.pms::class.java, findSuper = true) { - name == "getInstallerPackageName" - }.hookBefore { param -> - val query = param.args[0] as String? - - val callingUid = Binder.getCallingUid() - if (callingUid == Constants.UID_SYSTEM) return@hookBefore - - val callingApps = getCallingApps(service, callingUid) - val callingHandle = UserHandle.getUserHandleForUid(callingUid) - - for (caller in callingApps) { - when (service.shouldHideInstallationSource(caller, query, callingHandle)) { - Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME - Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = null - else -> continue - } - - service.increaseInstallerFilterCount(caller) - break - } - } - } - - fun applyPackageHiding( - methodName: String, - findCallingUid: () -> Int?, - findTargetApp: () -> String?, - findCallingApps: (Int) -> Array?, - applyReturnValue: () -> Unit, - ) { - val callingUid = findCallingUid() - if (callingUid == null || callingUid == Constants.UID_SYSTEM) return - val targetApp = findTargetApp() ?: return - logV(TAG) { "@$methodName incoming query: $callingUid => $targetApp" } - if (service.shouldHideFromUid(callingUid, targetApp) == true) { - applyReturnValue() - service.increasePMFilterCount(callingUid) - logD(TAG) { "@$methodName caller cache: $callingUid, target: $targetApp" } - return - } - val callingApps = findCallingApps(callingUid) - val caller = callingApps?.firstOrNull { service.shouldHide(it, targetApp) } - if (caller != null) { - logD(TAG) { "@$methodName caller: $callingUid $caller, target: $targetApp" } - applyReturnValue() - val last = lastFilteredApp.getAndSet(caller) - if (last != caller) logI(TAG) { "@${methodName}: query from $caller" } - service.putShouldHideUidCache(callingUid, caller, targetApp) - service.increasePMFilterCount(caller) - } - } - - final override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsPackageEventsHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsPackageEventsHook.kt deleted file mode 100644 index 312c3ad45..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsPackageEventsHook.kt +++ /dev/null @@ -1,57 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PACKAGE_MANAGER_SERVICE_CLASS - -class PmsPackageEventsHook(private val service: HMAService) : IFrameworkHook { - private var hook: XC_MethodHook.Unhook? = null - - override fun load() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - try { - hook = findMethod("com.android.server.pm.BroadcastHelper") { - name == "sendPackageBroadcastAndNotify" - }.hookBefore { param -> - service.handlePackageEvent( - param.args[0] as String?, - param.args[1] as String?, - param.args[2] as Bundle?, - ) - } - } catch (_: Throwable) { - hook = findMethod("com.android.internal.content.PackageMonitor") { - name == "onReceive" - }.hookBefore { param -> - val intent = param.args[1] as? Intent ?: return@hookBefore - - service.handlePackageEvent( - intent.action, - intent.data?.encodedSchemeSpecificPart, - intent.extras, - ) - } - } - } else { - hook = findMethod(PACKAGE_MANAGER_SERVICE_CLASS) { - name == "sendPackageBroadcast" - }.hookBefore { param -> - service.handlePackageEvent( - param.args[0] as String?, - param.args[1] as String?, - param.args[2] as Bundle?, - ) - } - } - } - - override fun unload() { - hook?.unhook() - hook = null - } -} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ZygoteHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ZygoteHook.kt deleted file mode 100644 index 869a0a788..000000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ZygoteHook.kt +++ /dev/null @@ -1,51 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed.hook - -import com.github.kyuubiran.ezxhelper.utils.findMethodOrNull -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook -import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logV -import icu.nullptr.hidemyapplist.xposed.XposedConstants.ZYGOTE_PROCESS_CLASS - -class ZygoteHook(private val service: HMAService): IFrameworkHook { - companion object { - private const val TAG = "ZygoteHook" - } - - private val hooks = mutableListOf() - - override fun load() { - findMethodOrNull(ZYGOTE_PROCESS_CLASS) { - name == "start" - }?.hookBefore { param -> - logV(TAG) { "@startZygoteProcess: Starting ${param.args.contentToString()}" } - - // ignore if the GIDs array is null - val gIDsIndex = param.args.indexOfFirst { it is IntArray } - if (gIDsIndex < 0) return@hookBefore - - val caller = param.args.lastOrNull { it is String } as String? ?: return@hookBefore - var perms = service.getRestrictedZygotePermissions(caller) ?: return@hookBefore - if (perms.isNotEmpty()) { - val gIDs = param.args[gIDsIndex] as IntArray - - // add more security, reject if not available in GID_PAIRS - perms = perms.filter { Constants.GID_PAIRS.containsValue(it) } - - logD(TAG) { "@startZygoteProcess: GIDs are ${gIDs.contentToString()}, removing $perms now" } - param.args[gIDsIndex] = gIDs.filter { it !in perms }.toIntArray() - service.increaseOthersFilterCount(caller) - } - }?.let { - logD(TAG) { "Loaded ZygoteProcess start hook!" } - hooks += it - } - } - - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() - } -} diff --git a/zygote/.gitignore b/zygote/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/zygote/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/zygote/build.gradle.kts b/zygote/build.gradle.kts new file mode 100644 index 000000000..10b6a7ae5 --- /dev/null +++ b/zygote/build.gradle.kts @@ -0,0 +1,86 @@ +import com.android.ide.common.signing.KeystoreHelper +import com.v7878.zygisk.gradle.ZygoteLoader +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.io.PrintStream +import java.util.Locale + +plugins { + alias(libs.plugins.agp.app) + alias(libs.plugins.kotlin) + alias(libs.plugins.com.github.aerathstuff.zygoteloader) +} + +val appPackageName: String by rootProject.extra + +android { + namespace = "$appPackageName.zygote" + + defaultConfig { + applicationId = namespace + } +} + +afterEvaluate { + android.applicationVariants.forEach { variant -> + val variantCapped = variant.name.replaceFirstChar { it.titlecase(Locale.ROOT) } + val variantLowered = variant.name.lowercase(Locale.ROOT) + + val outSrcDir = layout.buildDirectory.dir("generated/source/signInfo/${variantLowered}") + val outSrc = outSrcDir.get().file("org/frknkrc44/hma_oss/zygote/Magic.java") + val signInfoTask = tasks.register("generate${variantCapped}SignInfo") { + outputs.file(outSrc) + doLast { + val sign = android.buildTypes[variantLowered].signingConfig + outSrc.asFile.parentFile.mkdirs() + val certificateInfo = KeystoreHelper.getCertificateInfo( + sign?.storeType, + sign?.storeFile, + sign?.storePassword, + sign?.keyPassword, + sign?.keyAlias + ) + PrintStream(outSrc.asFile).apply { + println("package org.frknkrc44.hma_oss.zygote;") + 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, outSrcDir.get().asFile) + + val kotlinCompileTask = tasks.findByName("compile${variantCapped}Kotlin") as KotlinCompile + kotlinCompileTask.dependsOn(signInfoTask) + val srcSet = objects.sourceDirectorySet("magic", "magic").srcDir(outSrcDir) + kotlinCompileTask.source(srcSet) + } +} + +zygisk { + // inject to system_server + packages(ZygoteLoader.PACKAGE_SYSTEM_SERVER) + + // module properties + id = "hma_oss_zygisk" + name = "HMA-OSS Zygisk" + author = "frknkrc44" + description = "A Zygisk backend for HMA-OSS" + entrypoint = "org.frknkrc44.hma_oss.zygote.ZygoteEntry" + archiveName = "${rootProject.name}-ZYGISK-${android.defaultConfig.versionName}" + isAddVariantToArchiveName = true +} + +dependencies { + implementation(projects.common) + + implementation(libs.androidx.annotation.jvm) + implementation(libs.com.android.tools.build.apksig) + implementation(libs.io.github.vova7878.androidvmtools) + implementation(libs.io.github.vova7878.r8annotations) + implementation(libs.dev.rikka.hidden.compat) + + compileOnly(libs.dev.rikka.hidden.stub) +} \ No newline at end of file diff --git a/zygote/proguard-rules.pro b/zygote/proguard-rules.pro new file mode 100644 index 000000000..8ac893e42 --- /dev/null +++ b/zygote/proguard-rules.pro @@ -0,0 +1,27 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-keep class org.frknkrc44.hma_oss.zygote.ZygoteEntry { premain(); main(); } +-dontwarn android.content.pm.IPackageManager +-dontwarn android.content.pm.ParceledListSlice +-dontwarn android.os.SystemProperties +-dontwarn com.v7878.r8.annotations.KeepCodeAttribute diff --git a/zygote/src/main/AndroidManifest.xml b/zygote/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/zygote/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/zygote/src/main/assets/action.sh b/zygote/src/main/assets/action.sh new file mode 100644 index 000000000..b0e8bb5f2 --- /dev/null +++ b/zygote/src/main/assets/action.sh @@ -0,0 +1,3 @@ +#!/system/bin/sh + +am start org.frknkrc44.hma_oss/org.frknkrc44.hma_oss.ui.activity.MainActivity diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/ZygoteEntry.java b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/ZygoteEntry.java new file mode 100644 index 000000000..255c7713e --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/ZygoteEntry.java @@ -0,0 +1,43 @@ +package org.frknkrc44.hma_oss.zygote; + +import static org.frknkrc44.hma_oss.zygote.util.Logcat.logELegacy; +import static org.frknkrc44.hma_oss.zygote.util.Logcat.logILegacy; + +import com.v7878.r8.annotations.DoNotObfuscate; +import com.v7878.r8.annotations.DoNotObfuscateType; +import com.v7878.r8.annotations.DoNotShrink; +import com.v7878.r8.annotations.DoNotShrinkType; +import com.v7878.zygisk.ZygoteLoader; + +import org.frknkrc44.hma_oss.common.BuildConfig; +import org.frknkrc44.hma_oss.zygote.service.SystemServerHook; + +@SuppressWarnings("all") +@DoNotObfuscateType +@DoNotShrinkType +public class ZygoteEntry { + public static final String TAG = "ZygoteEntry"; + + @DoNotObfuscate + @DoNotShrink + public static void premain() throws Throwable { + + } + + @DoNotObfuscate + @DoNotShrink + public static void main() throws Throwable { + logILegacy(TAG, String.format("Injected into %s - %s", ZygoteLoader.getPackageName(), BuildConfig.APP_VERSION_NAME), null); + + try { + SystemServerHook.init(); + } catch (Throwable th) { + logELegacy(TAG, "An exception occurred while SystemServerHook init", th); + + // do not print "Done" if there is an issue + return; + } + + logILegacy(TAG, "Done", null); + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AccessibilityHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AccessibilityHook.kt new file mode 100644 index 000000000..c8094cf0f --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AccessibilityHook.kt @@ -0,0 +1,54 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.accessibilityservice.AccessibilityServiceInfo +import android.content.pm.ParceledListSlice +import icu.nullptr.hidemyapplist.common.settings_presets.AccessibilityPreset +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.ACCESSIBILITY_SERVICE_CLASS + +class AccessibilityHook(private val service: HMAService) : IFrameworkHook { + override val TAG = "AccessibilityHook" + + override fun load() { + BulkHooker.instance.apply { + hookBefore( + ACCESSIBILITY_SERVICE_CLASS, + "getEnabledAccessibilityServiceList", + ) { param -> + val callingApps = ServiceUtils.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + logD(TAG) { "@${param.methodName} returning empty list for ${callingApps.contentToString()}" } + + val returnedList = java.util.ArrayList() + param.result = if ("Parcel" in param.returnType.simpleName) { + ParceledListSlice(returnedList) + } else { + returnedList + } + } + } + + hookBefore( + ACCESSIBILITY_SERVICE_CLASS, + "addClient", + ) { param -> + val callingApps = ServiceUtils.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + param.result = 0L + } + } + } + } + + private fun callerIsSpoofed(caller: String) = + service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME) +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ActivityHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ActivityHook.kt new file mode 100644 index 000000000..3e8d917b8 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ActivityHook.kt @@ -0,0 +1,112 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.Intent +import android.content.pm.ResolveInfo +import android.os.Build +import icu.nullptr.hidemyapplist.common.CollectionUtils.firstWithType +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.OSUtils +import icu.nullptr.hidemyapplist.common.Utils +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getObjectField +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getStaticIntField +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.ACTIVITY_STACK_SUPERVISOR_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.ACTIVITY_STARTER_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.ACTIVITY_TASK_SUPERVISOR_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.COMPUTER_ENGINE_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PACKAGE_MANAGER_SERVICE_CLASS + +class ActivityHook(private val service: HMAService) : IFrameworkHook { + override val TAG = "ActivityHook" + + companion object { + private val fakeReturnCode by lazy { + getStaticIntField( + "android.app.ActivityManager", + "START_CLASS_NOT_FOUND", + ) + } + } + + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + ACTIVITY_STARTER_CLASS, + "execute", + ) { param -> + val request = getObjectField(param.thisObject, "mRequest") ?: return@hookBefore + val caller = getObjectField(request, "callingPackage") as String? + val intent = getObjectField(request, "intent") as Intent? + val targetApp = intent?.component?.packageName + + if (service.shouldHideActivityLaunch(caller, targetApp)) { + logD(TAG) { "@executeRequest: insecure query from $caller, target: ${intent?.component}" } + param.result = fakeReturnCode + service.increaseALFilterCount(caller) + } + } + + hookBefore( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + ACTIVITY_TASK_SUPERVISOR_CLASS + } else { + ACTIVITY_STACK_SUPERVISOR_CLASS + }, + "checkStartAnyActivityPermission", + ) { param -> + logV(TAG) { "${param.methodName}: ${param.args.contentToString()}" } + + // just an empty hook that does nothing + } + + if (!OSUtils.isSamsung()) { + hookBefore( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + COMPUTER_ENGINE_CLASS + } else { + PACKAGE_MANAGER_SERVICE_CLASS + }, + "applyPostResolutionFilter", + ) { param -> + @Suppress("UNCHECKED_CAST") // I know what I do + val list = param.args[1] as List? + if (list.isNullOrEmpty()) return@hookBefore + + val callingUid = param.args.firstWithType() + if (callingUid == Constants.UID_SYSTEM) return@hookBefore + + val callingApps = getCallingApps(service, callingUid) + val caller = callingApps.firstOrNull { service.isHookEnabled(it) } + if (caller != null) { + logV(TAG) { "@${param.methodName}: $caller requested a resolve info" } + + val filteredList = list.filter { resolveInfo -> + val targetApp = Utils.getPackageNameFromResolveInfo(resolveInfo) + + logV(TAG) { "@${param.methodName}: Checking $targetApp for $caller" } + + (!service.shouldHideActivityLaunch(caller, targetApp)).apply { + if (!this) { + logD(TAG) { "@${param.methodName}: Filtered $targetApp from $caller" } + } + } + } + + if (filteredList.size != list.size) { + param.setArgument(1, filteredList.toList()) + + service.increasePMFilterCount(caller) + } + } + } + } + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AppDataIsolationHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AppDataIsolationHook.kt new file mode 100644 index 000000000..580a05508 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/AppDataIsolationHook.kt @@ -0,0 +1,212 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.annotation.SuppressLint +import android.os.Build +import android.os.SystemProperties +import androidx.annotation.RequiresApi +import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.service.SystemServerHook +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getBooleanField +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getIntField +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getObjectField +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.setBooleanField +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PROCESS_LIST_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PROCESS_RECORD_INTERNAL_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.STORAGE_MANAGER_SERVICE_CLASS +import java.util.Map + +@SuppressLint("PrivateApi") +@RequiresApi(Build.VERSION_CODES.R) +class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { + override val TAG = "AppDataIsolationHook" + + companion object { + private const val APPDATA_ISOLATION_ENABLED = "mAppDataIsolationEnabled" + private const val VOLD_APPDATA_ISOLATION_ENABLED = "mVoldAppDataIsolationEnabled" + private const val FUSE_PROP = "persist.sys.fuse" + } + + private var voldHookSkipped = false + + private val processRecordIntClass: Class<*> by lazy { + Class.forName( + PROCESS_RECORD_INTERNAL_CLASS, + true, + SystemServerHook.classLoader, + ) + } + + @SuppressLint("PrivateApi") + override fun load() { + if (!(service.config.altAppDataIsolation || service.config.altVoldAppDataIsolation)) return + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + PROCESS_LIST_CLASS, + "startProcess", + ) { param -> + if (service.config.altAppDataIsolation) { + val isEnabled = getBooleanField( + param.thisObject, + APPDATA_ISOLATION_ENABLED, + ) + + if (!isEnabled) { + setBooleanField( + param.thisObject, + APPDATA_ISOLATION_ENABLED, + true + ) + + logI(TAG) { "ProcessList - App data isolation is forced" } + } + } + + if (service.config.altVoldAppDataIsolation && !voldHookSkipped) { + val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) + + if (!fuseEnabled) { + voldHookSkipped = true + logE(TAG) { "ProcessList - FUSE storage is not enabled, skip vold hook" } + } else { + val isolationEnabled = getBooleanField( + param.thisObject, + VOLD_APPDATA_ISOLATION_ENABLED + ) + + if (!isolationEnabled) { + setBooleanField( + param.thisObject, + VOLD_APPDATA_ISOLATION_ENABLED, + true + ) + + logI(TAG) { "ProcessList - Vold app data isolation is forced" } + } + } + } + } + + hookAfter( + PROCESS_LIST_CLASS, + "needsStorageDataIsolation", + ) { param -> + if (service.config.altVoldAppDataIsolation) { + val app = param.args.find { it?.javaClass?.simpleName == "ProcessRecord" }!! + val uid = runCatching { + getIntField(app, "uid") + }.getOrElse { + getIntField(app, "uid", processRecordIntClass) + } + + val apps = getCallingApps(service, uid) + + if (HMAService.instance?.config?.detailLog == true) { + val processName = runCatching { + getObjectField(app, "processName") + }.getOrElse { + getObjectField(app, "processName", processRecordIntClass) + } + val mountNode = runCatching { + getIntField(app, "mMountMode") + }.getOrDefault(0) + val isolated = runCatching { + getBooleanField(app, "isolated") + }.getOrElse { + getBooleanField(app, "isolated", processRecordIntClass) + } + val appZygote = runCatching { + getBooleanField(app, "appZygote") + }.getOrElse { + getBooleanField(app, "appZygote", processRecordIntClass) + } + + logD(TAG) { "@needsStorageDataIsolation $uid and ${apps.contentToString()} - $processName value without override: ${param.result}, mount node: $mountNode, isolated: $isolated, appZygote: $appZygote" } + } + + // Do not isolate this module for safety + if (apps.contains(BuildConfig.APP_PACKAGE_NAME)) { + param.result = false + return@hookAfter + } + + if (apps.any { service.isAppDataIsolationExcluded(it) }) { + param.result = false + return@hookAfter + } + + if (service.config.skipSystemAppDataIsolation) { + val isSystemApp = service.systemApps.any { apps.contains(it) } + logD(TAG) { "@needsStorageDataIsolation $uid and ${apps.contentToString()} - isSystemApp: $isSystemApp" } + + if (isSystemApp) { + param.result = false + return@hookAfter + } + } + } + } + + hookBefore( + STORAGE_MANAGER_SERVICE_CLASS, + "onVolumeStateChangedLocked", + ) { param -> + if (service.config.altVoldAppDataIsolation && !voldHookSkipped) { + val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) + + if (!fuseEnabled) { + logE(TAG) { "StorageManagerService - FUSE storage is not enabled, skip vold hook" } + voldHookSkipped = true + return@hookBefore + } + + val isolationEnabled = getBooleanField( + param.thisObject, + VOLD_APPDATA_ISOLATION_ENABLED + ) + + if (!isolationEnabled) { + setBooleanField( + param.thisObject, + VOLD_APPDATA_ISOLATION_ENABLED, + true + ) + + logI(TAG) { "StorageManagerService - Vold app data isolation is forced" } + } + } + } + + hookBefore( + STORAGE_MANAGER_SERVICE_CLASS, + "remountAppStorageDirs", + ) { param -> + if (!voldHookSkipped && service.config.altVoldAppDataIsolation && service.config.skipSystemAppDataIsolation) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + val pidPkgMap = param.getArgument(1) as Map<*, *> + val keysToRemove = mutableSetOf() + + for (entry in pidPkgMap.entrySet()) { + val pid = entry.key + val packageName = entry.value as String + + if (packageName in service.systemApps || packageName == BuildConfig.APP_PACKAGE_NAME) { + logD(TAG) { "@remountAppStorageDirs SYSTEM $pid - $packageName is marked to remove" } + keysToRemove += pid + break + } + } + + keysToRemove.forEach { pidPkgMap.remove(it) } + } + } + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ContentProviderHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ContentProviderHook.kt new file mode 100644 index 000000000..271e85213 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ContentProviderHook.kt @@ -0,0 +1,177 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.AttributionSource +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import icu.nullptr.hidemyapplist.common.CollectionUtils.firstWithType +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.service.HookParam +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.CONTENT_PROVIDER_TRANSPORT_CLASS + +class ContentProviderHook(private val service: HMAService): IFrameworkHook { + override val TAG = "ContentProviderHook" + + companion object { + private val NV_PAIR = arrayOf("name", "value") + } + + @Suppress("UNCHECKED_CAST") + override fun load() { + BulkHooker.instance.apply { + hookAfter( + CONTENT_PROVIDER_TRANSPORT_CLASS, + "query", + ) { param -> + val callingApps = getCallingPackages(param) + + val caller = callingApps.firstOrNull { service.isAnySettingsReplacementsEnabled(it) } + if (caller == null) return@hookAfter + + val uriIdx = param.args.indexOfFirst { it is Uri } + val uri = param.args[uriIdx] as Uri + + if (uri.authority != "settings") return@hookAfter + + val segments = uri.pathSegments + if (segments.isEmpty()) return@hookAfter + + logD(TAG) { + val projection = param.args[uriIdx + 1] as Array? + val args = param.args[uriIdx + 2] as Bundle? + + "@spoofSettings QUERY in ${callingApps.contentToString()}: $uri, ${projection?.contentToString()}, $args" + } + + val database = segments[0] + + if (segments.size >= 2) { + val name = segments[1] + + logD(TAG) { "@spoofSettings QUERY received caller: $caller, database: $database, name: $name" } + + val replacement = service.getSpoofedSetting(caller, name, database) + if (replacement != null) { + logD(TAG) { "@spoofSettings QUERY $name in $database replaced for $caller" } + param.result = MatrixCursor(arrayOf("name", "value"), 1).apply { + addRow(arrayOf(replacement.name, replacement.value)) + } + + service.increaseSettingsFilterCount(caller) + } + } else { + logD(TAG) { "@spoofSettings LIST_QUERY received caller: $caller, database: $database" } + + val result = param.result as? Cursor? ?: return@hookAfter + + val columns = mutableMapOf>().apply { + for (i in 0 ..< result.columnCount) { + put(result.getColumnName(i), mutableListOf()) + } + } + + logD(TAG) { "@spoofSetting LIST_QUERY columns: ${columns.keys}" } + + val keyColumn = columns["name"] + val valueColumn = columns["value"] + + if (keyColumn == null || valueColumn == null) { + logD(TAG) { "@spoofSettings LIST_QUERY invalid query: $caller ($keyColumn, $valueColumn)" } + return@hookAfter + } + + var filteredEntryCount = 0 + + while (result.moveToNext()) { + val name = result.getString(columns.keys.indexOf("name")) + keyColumn.add(name) + + val replacement = service.getSpoofedSetting(caller, name, database) + val value = if (replacement != null) { + logD(TAG) { "@spoofSettings QUERY $name in $database replaced for $caller" } + + filteredEntryCount++ + + replacement.value + } else { + result.getString(columns.keys.indexOf("value")) + } + + valueColumn.add(value) + + if (columns.keys.size > 2) { + for (otherCol in columns.keys.filter { it !in NV_PAIR }) { + val other = result.getString(columns.keys.indexOf(otherCol)) + + columns[otherCol]!!.add(other) + } + } + } + + service.increaseSettingsFilterCount(caller, filteredEntryCount) + + param.result = MatrixCursor(columns.keys.toTypedArray(), columns.size).apply { + val size = columns.values.first().size + for (i in 0 ..< size) { + val innerList = mutableListOf() + + columns.values.forEach { colVal -> + innerList.add(colVal[i]) + } + + addRow(innerList) + } + } + } + } + + hookBefore( + CONTENT_PROVIDER_TRANSPORT_CLASS, + "call", + ) { param -> + val callingApps = getCallingPackages(param) + val caller = callingApps.firstOrNull { service.isAnySettingsReplacementsEnabled(it) } + if (caller == null) return@hookBefore + + val nameIdx = param.args.indexOfLast { it is String } + val name = param.args[nameIdx] as String? + val method = param.args[nameIdx - 1] as String? + + logD(TAG) { "@spoofSettings CALL received caller: ${callingApps.contentToString()}, method: $method, name: $name" } + + when (method) { + "GET_global", "GET_secure", "GET_system" -> { + val database = method.substring(method.indexOf('_') + 1) + val replacement = service.getSpoofedSetting(caller, name, database) + if (replacement != null) { + logD(TAG) { "@spoofSettings CALL $name in $database replaced for $caller" } + param.result = Bundle().apply { + putString(Settings.NameValueTable.VALUE, replacement.value) + putInt("_generation_index", -1) + } + + service.increaseSettingsFilterCount(caller) + } + } + } + } + } + } + + private fun getCallingPackages(param: HookParam): Array = try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val attrSource = param.args.firstWithType() + arrayOf(attrSource.packageName!!) + } else { + arrayOf(param.args.firstWithType()) + } + } catch (_: Throwable) { + ServiceUtils.getCallingApps(service) + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/IFrameworkHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/IFrameworkHook.kt new file mode 100644 index 000000000..3a6f48e18 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/IFrameworkHook.kt @@ -0,0 +1,9 @@ +package org.frknkrc44.hma_oss.zygote.hook + +interface IFrameworkHook { + @Suppress("PropertyName") + val TAG: String + + fun load() + fun onConfigChanged() {} +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ImmHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ImmHook.kt new file mode 100644 index 000000000..c3f00bb3c --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ImmHook.kt @@ -0,0 +1,295 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.ComponentName +import android.content.pm.PackageManager +import android.os.Binder +import android.os.Build +import android.provider.Settings +import android.view.inputmethod.InputMethodInfo +import android.view.inputmethod.InputMethodSubtype +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.Utils +import icu.nullptr.hidemyapplist.common.settings_presets.InputMethodPreset +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.service.HookParam +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.Logcat.logW +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageManager +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.callStaticMethod +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.IMM_IMPL_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.IMM_SERVICE_CLASS +import java.util.Collections + +class ImmHook(private val service: HMAService) : IFrameworkHook { + override val TAG = "ImmHook" + + // TODO: Find a method to get settings activity + fun getFakeInputMethodInfo(packageName: String): InputMethodInfo { + val defaultInputMethod = service.getSpoofedSetting( + packageName, + Settings.Secure.DEFAULT_INPUT_METHOD, + Constants.SETTINGS_SECURE, + ) + + if (defaultInputMethod?.value != null) { + try { + val component = ComponentName.unflattenFromString(defaultInputMethod.value!!)!! + logD(TAG) { "Package component: \"$component\"" } + + val pkgManager = getPackageManager() + val kbdPackage = Utils.binderLocalScope { + pkgManager.getApplicationInfo(component.packageName, 0) + } + + return InputMethodInfo( + component.packageName, + component.className, + kbdPackage.loadLabel(pkgManager), + null, + ) + } catch (e: Throwable) { + logV(TAG, e) { e.message ?: "" } + } + } + + return InputMethodInfo( + "com.google.android.inputmethod.latin", + "com.android.inputmethod.latin.LatinIME", + "Gboard", + null, + ) + } + + @Suppress("UNCHECKED_CAST") + override fun load() { + // OEMs (especially Samsung and Xiaomi) messes up whole framework code, + // so nothing left except messing up this code + BulkHooker.instance.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + findAltMethod( + listOf(IMM_SERVICE_CLASS, IMM_IMPL_CLASS), + listOf("getCurrentInputMethodInfoAsUser"), + )?.let { method -> + hookBefore( + method.declaringClass.name, + method.name, + ) { param -> + val callingApps = getCallingApps(service) + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + logD(TAG) { "@${param.methodName} spoofed input method for $caller" } + + val fakeIMInfo = getFakeInputMethodInfo(caller) + val userHandle = param.getArgument(1) as Int + if (!isIMExists(fakeIMInfo.packageName, userHandle)) { + warnNotInstalledKeyboard(param.methodName, fakeIMInfo.packageName) + } + + param.result = fakeIMInfo + service.increaseSettingsFilterCount(caller) + } + } + } + } + + findAltMethod( + listOf(IMM_SERVICE_CLASS), + listOf("getInputMethodList", "getInputMethodListInternal"), + )?.let { method -> + hookAfter( + method.declaringClass.name, + method.name, + ) { param -> + logD(TAG) { "@${param.methodName}: hook init" } + + val currentResult = param.result ?: return@hookAfter + logD(TAG) { "@${param.methodName}: Result: $currentResult Args: ${param.args.contentToString()}" } + + val callingUid = if (param.args.count { it is Int } > 2) { + param.args.lastOrNull { it is Int && it > 999 } as? Int ?: return@hookAfter + } else { + Binder.getCallingUid() + } + + logD(TAG) { "@${param.methodName}: Caller ID: $callingUid" } + + val returnType = param.returnType + if (returnType.simpleName == "InputMethodInfoSafeList") { + val inList = callStaticMethod( + currentResult.javaClass, + "extractFrom", + currentResult + ) as List + + val newImmList = calculateReturnedInputMethodList(callingUid, inList) + + param.result = returnType.getDeclaredMethod( + "create", + List::class.java, + ).apply { isAccessible = true }.invoke(null, newImmList) + } else { + param.result = calculateReturnedInputMethodList( + callingUid, currentResult as List) + } + } + } + + findAltMethod( + listOf(IMM_SERVICE_CLASS), + listOf("getEnabledInputMethodList", "getEnabledInputMethodListInternal"), + )?.let { method -> + hookBefore( + method.declaringClass.name, + method.name, + ) { param -> + val callingApps = getCallingApps(service) + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + logD(TAG) { "@${param.methodName}: spoofed input method for $caller" } + + val fakeIMInfo = getFakeInputMethodInfo(caller) + if (!isIMExists(fakeIMInfo.packageName)) { + warnNotInstalledKeyboard(param.methodName, fakeIMInfo.packageName) + } + + listOf(fakeIMInfo).let { list -> + val returnType = param.returnType + param.result = if (returnType.simpleName == "InputMethodInfoSafeList") { + returnType.getDeclaredMethod( + "create", + List::class.java, + ).apply { isAccessible = true }.invoke(null, list) + } else { list } + } + + service.increaseSettingsFilterCount(caller) + } + } + } + + findAltMethod( + listOf(IMM_SERVICE_CLASS, IMM_IMPL_CLASS), + listOf("getCurrentInputMethodSubtype"), + )?.let { + hookBefore( + it.declaringClass.name, + it.name, + ) { param -> + subtypeHook(param) + } + } + + findAltMethod( + listOf(IMM_SERVICE_CLASS, IMM_IMPL_CLASS), + listOf("getLastInputMethodSubtype"), + )?.let { + hookBefore( + it.declaringClass.name, + it.name, + ) { param -> + subtypeHook(param) + } + } + + findAltMethod( + listOf(IMM_SERVICE_CLASS, IMM_IMPL_CLASS), + listOf("getEnabledInputMethodSubtypeListInternal", "getEnabledInputMethodSubtypeList") + )?.let { + hookBefore( + it.declaringClass.name, + it.name, + ) { param -> + subtypeListHook(param) + } + } + } + } + + private fun subtypeHook(param: HookParam) { + val callingApps = getCallingApps(service) + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + logD(TAG) { "@${param.methodName}: spoofed input method subtype for ${callingApps.contentToString()}" } + + // TODO: Find a method to get exact value for spoofed input method + param.result = null + service.increaseSettingsFilterCount(caller) + } + } + + private fun subtypeListHook(param: HookParam) { + val callingApps = getCallingApps(service) + + val caller = callingApps.firstOrNull { callerIsSpoofed(it) } + if (caller != null) { + logD(TAG) { "@${param.methodName}: spoofed input method subtype for ${callingApps.contentToString()}" } + + // TODO: Find a method to get exact list for spoofed input method + Collections.emptyList().let { list -> + val returnType = param.returnType + param.result = if (returnType.simpleName == "InputMethodSubtypeSafeList") { + returnType.getDeclaredMethod( + "create", + List::class.java, + ).apply { isAccessible = true }.invoke(null, list) + } else { list } + } + + service.increaseSettingsFilterCount(caller) + } + } + + fun calculateReturnedInputMethodList(callingUid: Int, inList: List): List { + logV(TAG) { "@getInputMethodList*calculator: $callingUid - Current: ${inList.map { it.component }}" } + + val caller = getCallingApps(service, callingUid) + .firstOrNull { callerIsSpoofed(it) } ?: return inList + + logD(TAG) { "@getInputMethodList: spoofed input method for $caller" } + + val calculatedList = inList.filter { imInfo -> + !service.shouldHide(caller, imInfo.packageName) + } + + logV(TAG) { "@getInputMethodList*calculator: $callingUid - Calculated: ${calculatedList.map { it.component }}" } + + val fakeIMInfo = getFakeInputMethodInfo(caller) + val imExists = isIMExists(fakeIMInfo.packageName) + val calcListHasIM = calculatedList.any { it.packageName == fakeIMInfo.packageName } + if (!(imExists && calcListHasIM)) { + if (!imExists) { + warnNotInstalledKeyboard("getInputMethodList*calculator", fakeIMInfo.packageName) + } + + if (!calcListHasIM) { + return (calculatedList + fakeIMInfo).sortedWith { info1, info2 -> + info1.packageName.compareTo(info2.packageName) + } + } + } + + return calculatedList + } + + private fun isIMExists(packageName: String, inUserId: Int? = null): Boolean { + if (packageName in service.systemApps) return true + + val userId = inUserId ?: Binder.getCallingUserHandle().hashCode() + return Utils.binderLocalScope { + Utils.getPackageUidCompat(service.pms, packageName, PackageManager.MATCH_ALL.toLong(), userId) >= 0 + } + } + + private fun warnNotInstalledKeyboard(methodName: String, packageName: String) { + logW(TAG) { "@$methodName: PROBABLY spoofing for a not installed keyboard, please install $packageName or spoof for another keyboard by using settings templates to reduce detections. Do not care this message if you are sure the keyboard is installed correctly." } + } + + private fun callerIsSpoofed(caller: String) = + service.getEnabledSettingsPresets(caller).contains(InputMethodPreset.NAME) +} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PlatformCompatHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PlatformCompatHook.kt similarity index 60% rename from xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PlatformCompatHook.kt rename to zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PlatformCompatHook.kt index 42c71b0c8..814ed1f5a 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PlatformCompatHook.kt +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PlatformCompatHook.kt @@ -1,46 +1,41 @@ -package icu.nullptr.hidemyapplist.xposed.hook +package org.frknkrc44.hma_oss.zygote.hook import android.content.pm.ApplicationInfo import android.os.Build import androidx.annotation.RequiresApi -import com.github.kyuubiran.ezxhelper.utils.findMethod -import com.github.kyuubiran.ezxhelper.utils.hookBefore -import de.robv.android.xposed.XC_MethodHook import icu.nullptr.hidemyapplist.common.PropertyUtils -import icu.nullptr.hidemyapplist.xposed.HMAService -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.XposedConstants.PLATFORM_COMPAT_CLASS import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PLATFORM_COMPAT_CLASS @RequiresApi(Build.VERSION_CODES.R) class PlatformCompatHook(private val service: HMAService) : IFrameworkHook { - - companion object { - private const val TAG = "PlatformCompatHook" - } + override val TAG = "PlatformCompatHook" private val sAppDataIsolationEnabled by lazy { PropertyUtils.isAppDataIsolationEnabled || service.config.altAppDataIsolation } - private var hook: XC_MethodHook.Unhook? = null - override fun load() { if (!service.config.forceMountData) return logI(TAG) { "Load hook" } logI(TAG) { "App data isolation enabled: $sAppDataIsolationEnabled" } - hook = findMethod(PLATFORM_COMPAT_CLASS) { - name == "isChangeEnabled" - }.hookBefore { param -> + + BulkHooker.instance.hookBefore( + PLATFORM_COMPAT_CLASS, + "isChangeEnabled", + ) { param -> runCatching { if (!sAppDataIsolationEnabled) return@hookBefore - val changeId = param.args[0] as Long + val changeId = param.getArgument(1) as Long if (changeId != 143937733L) return@hookBefore - val appInfo = param.args[1] as ApplicationInfo + val appInfo = param.getArgument(2) as ApplicationInfo val app = appInfo.packageName if (app == BuildConfig.APP_PACKAGE_NAME || app in service.systemApps) return@hookBefore if (service.isHookEnabled(app)) { @@ -49,21 +44,17 @@ class PlatformCompatHook(private val service: HMAService) : IFrameworkHook { } }.onFailure { logE(TAG, it) { "Fatal error occurred, disable hooks" } - unload() } } } - override fun unload() { - hook?.unhook() - hook = null - } - override fun onConfigChanged() { + /* if (service.config.forceMountData) { if (hook == null) load() } else { if (hook != null) unload() } + */ } } diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget29.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget29.kt new file mode 100644 index 000000000..33e29e0ab --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget29.kt @@ -0,0 +1,66 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PACKAGE_MANAGER_SERVICE_CLASS + +class PmsHookTarget29(service: HMAService) : PmsHookTargetBase(service) { + + override val TAG = "PmsHookTarget29" + + // not required until SDK 30 + override val fakeSystemPackageInstallSourceInfo = null + override val fakeUserPackageInstallSourceInfo = null + + @Suppress("UNCHECKED_CAST") + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + service.pms::class.java.name, + "filterAppAccessLPr", + paramCount = 5, + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(2) as Int? }, + { getPackageNameFromPackageSettings(param.getArgument(1)) }, + { getCallingApps(service, it) }, + { param.result = true }, + ) + } + + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "getPackageInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(4) as Int? }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "getApplicationInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(3) as Int? }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + } + + super.load() + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget30.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget30.kt new file mode 100644 index 000000000..9d7354c69 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget30.kt @@ -0,0 +1,104 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.os.Binder +import android.os.Build +import androidx.annotation.RequiresApi +import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findConstructor +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.APPS_FILTER_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PACKAGE_MANAGER_SERVICE_CLASS + +@RequiresApi(Build.VERSION_CODES.R) +class PmsHookTarget30(service: HMAService) : PmsHookTargetBase(service) { + + override val TAG = "PmsHookTarget30" + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 4, + )!!.newInstance( + null, + null, + null, + null, + ) + } + + override val fakeUserPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 4, + )!!.newInstance( + VENDING_PACKAGE_NAME, + psPackageInfo?.signingInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + ) + } + + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "getPackageSetting", + ) { param -> + applyPackageHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + APPS_FILTER_CLASS, + "shouldFilterApplication", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(1) as Int }, + { getPackageNameFromPackageSettings(param.getArgument(3)) }, + { getCallingApps(service, it) }, + { param.result = true }, + ) + } + + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "getPackageInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(4) as? Int }, + { param.getArgument(1) as? String }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "getApplicationInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(3) as? Int }, + { param.getArgument(1) as? String }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + } + + super.load() + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget31.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget31.kt new file mode 100644 index 000000000..3cf1886da --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget31.kt @@ -0,0 +1,117 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.os.Binder +import android.os.Build +import androidx.annotation.RequiresApi +import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findConstructor +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.APPS_FILTER_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PMS_COMPUTER_TRACKER_CLASS + +@RequiresApi(Build.VERSION_CODES.S) +class PmsHookTarget31(service: HMAService) : PmsHookTargetBase(service) { + + override val TAG = "PmsHookTarget31" + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 4, + )!!.newInstance( + null, + null, + null, + null, + ) + } + + override val fakeUserPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 4, + )!!.newInstance( + VENDING_PACKAGE_NAME, + psPackageInfo?.signingInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + ) + } + + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + PMS_COMPUTER_TRACKER_CLASS, + "getPackageSetting", + ) { param -> + applyPackageHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + PMS_COMPUTER_TRACKER_CLASS, + "getPackageSettingInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(2) as Int? }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + PMS_COMPUTER_TRACKER_CLASS, + "getPackageInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(4) as Int? }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + PMS_COMPUTER_TRACKER_CLASS, + "getApplicationInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(3) as Int? }, + { param.getArgument(1) as String? }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + APPS_FILTER_CLASS, + "shouldFilterApplication", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(1) as Int? }, + { getPackageNameFromPackageSettings(param.getArgument(3)) }, + { getCallingApps(service, it) }, + { param.result = true }, + ) + } + } + + super.load() + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget33.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget33.kt new file mode 100644 index 000000000..1af055914 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget33.kt @@ -0,0 +1,82 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.pm.PackageInstaller +import android.os.Build +import androidx.annotation.RequiresApi +import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME +import icu.nullptr.hidemyapplist.common.Utils +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findConstructor +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findMethod +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.APPS_FILTER_IMPL_CLASS + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +class PmsHookTarget33(service: HMAService) : PmsHookTargetBase(service) { + + override val TAG = "PmsHookTarget33" + + private val getPackagesForUidMethod by lazy { + findMethod( + "com.android.server.pm.Computer", + "getPackagesForUid", + isDeclared = false, + systemClassLoader = true, + Int::class.java, + ) + } + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 5, + )!!.newInstance( + null, + null, + null, + null, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, + ) + } + + override val fakeUserPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 5, + )!!.newInstance( + VENDING_PACKAGE_NAME, + psPackageInfo?.signingInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + PackageInstaller.PACKAGE_SOURCE_STORE, + ) + } + + @Suppress("UNCHECKED_CAST") + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + APPS_FILTER_IMPL_CLASS, + "shouldFilterApplication", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(2) as Int? }, + { getPackageNameFromPackageSettings(param.getArgument(4)) }, + { + Utils.binderLocalScope { + getPackagesForUidMethod.invoke(param.getArgument(1), it) as Array? + } + }, + { param.result = true }, + ) + } + } + + super.load() + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget34.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget34.kt new file mode 100644 index 000000000..7b2f344c3 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTarget34.kt @@ -0,0 +1,108 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.pm.PackageInstaller +import android.os.Binder +import android.os.Build +import androidx.annotation.RequiresApi +import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME +import icu.nullptr.hidemyapplist.common.Utils +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findConstructor +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findMethod +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.APPS_FILTER_IMPL_CLASS +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PACKAGE_MANAGER_SERVICE_CLASS + +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +class PmsHookTarget34(service: HMAService) : PmsHookTargetBase(service) { + + override val TAG = "PmsHookTarget34" + + private val getPackagesForUidMethod by lazy { + findMethod( + "com.android.server.pm.Computer", + "getPackagesForUid", + isDeclared = false, + systemClassLoader = true, + Int::class.java, + ) + } + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 6, + )!!.newInstance( + null, + null, + null, + null, + null, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, + ) + } + + override val fakeUserPackageInstallSourceInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo", + 6, + )!!.newInstance( + VENDING_PACKAGE_NAME, + psPackageInfo?.signingInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + PackageInstaller.PACKAGE_SOURCE_STORE, + ) + } + + @Suppress("UNCHECKED_CAST") + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + hookBefore( + APPS_FILTER_IMPL_CLASS, + "shouldFilterApplication", + ) { param -> + applyPackageHiding( + param.methodName, + { param.getArgument(2) as Int? }, + { getPackageNameFromPackageSettings(param.getArgument(4)) }, + { + Utils.binderLocalScope { + getPackagesForUidMethod.invoke(param.getArgument(1), it) as Array? + } + }, + { param.result = true }, + ) + } + + // AOSP exploit - https://github.com/aosp-mirror/platform_frameworks_base/commit/5bc482bd99ea18fe0b4064d486b29d5ae2d65139 + // Only 14 QPR2+ has this method + // UPDATE: Samsung adds getArchivedPackage instead of getArchivedPackageInternal + val altNames = findAltMethod( + listOf(PACKAGE_MANAGER_SERVICE_CLASS), + listOf("getArchivedPackageInternal", "getArchivedPackage"), + ) ?: return@apply + + hookBefore( + altNames.declaringClass.name, + altNames.name, + ) { param -> + applyPackageHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1).toString() }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + } + + super.load() + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTargetBase.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTargetBase.kt new file mode 100644 index 000000000..f3f4e41b2 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsHookTargetBase.kt @@ -0,0 +1,296 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.pm.PackageManager +import android.os.Binder +import android.os.Build +import android.os.UserHandle +import android.util.ArrayMap +import icu.nullptr.hidemyapplist.common.CollectionUtils.firstOrNullWithType +import icu.nullptr.hidemyapplist.common.CollectionUtils.lastWithType +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.Constants.VENDING_PACKAGE_NAME +import icu.nullptr.hidemyapplist.common.OSUtils +import icu.nullptr.hidemyapplist.common.Utils +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.service.HMAServiceCache +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getCallingApps +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.getPackageNameFromPackageSettings +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.callMethod +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.COMPUTER_ENGINE_CLASS +import java.util.concurrent.atomic.AtomicReference + +abstract class PmsHookTargetBase(protected val service: HMAService) : IFrameworkHook { + + private val androidPkgClazzNames = arrayOf("AndroidPackage", "PackageImpl") + + protected var lastFilteredApp: AtomicReference = AtomicReference(null) + + protected val psPackageInfo by lazy { + try { + Utils.getPackageInfoCompat( + service.pms, + VENDING_PACKAGE_NAME, + PackageManager.GET_SIGNING_CERTIFICATES.toLong(), + 0 + ) + } catch (_: Throwable) { + null + } + } + + abstract val fakeSystemPackageInstallSourceInfo: Any? + abstract val fakeUserPackageInstallSourceInfo: Any? + + override fun load() { + BulkHooker.instance.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + hookAfter( + COMPUTER_ENGINE_CLASS, + "getPackageStates", + ) { param -> + val callingUid = Binder.getCallingUid() + if (callingUid == Constants.UID_SYSTEM) return@hookAfter + + val callingApps = getCallingApps(service, callingUid) + val caller = callingApps.firstOrNull { service.isHookEnabled(it) } + if (caller != null) { + logD(TAG) { "@getPackageStates: incoming query from $caller" } + + val result = param.result as ArrayMap<*, *> + val markedToRemove = mutableListOf() + + for (pair in result.entries) { + val packageSettings = pair.value + val packageName = getPackageNameFromPackageSettings(packageSettings) + if (service.shouldHide(caller, packageName)) { + markedToRemove.add(pair.key) + } + } + + if (markedToRemove.isNotEmpty()) { + val copyResult = ArrayMap(result) + copyResult.removeAll(markedToRemove) + logD(TAG) { "@getPackageStates: removed ${markedToRemove.size} entries from $caller" } + param.result = copyResult + service.increasePMFilterCount(caller) + } + } + } + + // Samsung related fix + if (OSUtils.isSamsung()) { + hookBefore( + COMPUTER_ENGINE_CLASS, + "generatePackageInfo", + ) { param -> + applyPackageHiding( + param.methodName, + { Binder.getCallingUid() }, + { getPackageNameFromPackageSettings(param.getArgument(1)) }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + } + + // Samsung devices can fail to get this hook working, + // but it is okay due to generatePackageInfo hook + hookBefore( + COMPUTER_ENGINE_CLASS, + "addPackageHoldingPermissions", + ) { param -> + applyPackageHiding( + param.methodName, + { Binder.getCallingUid() }, + { getPackageNameFromPackageSettings(param.getArgument(2)) }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + COMPUTER_ENGINE_CLASS, + "getPackageInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.args.firstOrNullWithType() }, + { param.args.firstOrNullWithType() }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + COMPUTER_ENGINE_CLASS, + "getApplicationInfoInternal", + ) { param -> + applyPackageHiding( + param.methodName, + { param.args.firstOrNullWithType() }, + { param.args.firstOrNullWithType() }, + { getCallingApps(service, it) }, + { param.result = null }, + ) + } + + hookBefore( + COMPUTER_ENGINE_CLASS, + "isCallerInstallerOfRecord", + ) { param -> + val callingUid = param.args.lastWithType() + + applyInstallerHiding( + param.methodName, + { callingUid }, + fta@{ + val pkg = param.args.lastOrNull { + it?.javaClass?.simpleName in androidPkgClazzNames + } ?: return@fta null + callMethod(pkg, + if (pkg.javaClass.simpleName == "PackageImpl") { + "getManifestPackageName" + } else { + "getPackageName" + } + ) as? String + } + ) { + when (it) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = callingUid == psPackageInfo?.applicationInfo?.uid + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = false + } + } + } + + hookBefore( + COMPUTER_ENGINE_CLASS, + "getInstallerPackageName", + ) { param -> + applyInstallerHiding( + param.methodName, + { param.args.firstOrNullWithType() ?: Binder.getCallingUid() }, + { param.args.firstOrNullWithType() }, + ) { + when (it) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = null + } + } + } + } else { + hookBefore( + service.pms.javaClass.name, + "getInstallerPackageName", + ) { param -> + applyInstallerHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1) as? String }, + ) { + when (it) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = null + } + } + } + } + + if (service.pmn != null) { + hookBefore( + service.pmn.javaClass.name, + "getInstallerForPackage", + ) { param -> + applyInstallerHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1) as? String }, + ) { + when (it) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = "preload" + } + } + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + hookBefore( + service.pms.javaClass.name, + "getInstallSourceInfo", + ) { param -> + applyInstallerHiding( + param.methodName, + { Binder.getCallingUid() }, + { param.getArgument(1) as? String } + ) { + when (it) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = fakeUserPackageInstallSourceInfo + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = fakeSystemPackageInstallSourceInfo + } + } + } + } + } + } + + fun applyPackageHiding( + methodName: String, + findCallingUid: () -> Int?, + findTargetApp: () -> String?, + findCallingApps: (Int) -> Array?, + applyReturnValue: () -> Unit, + ) { + val callingUid = findCallingUid() + if (callingUid == null || callingUid == Constants.UID_SYSTEM) return + val targetApp = findTargetApp() ?: return + logV(TAG) { "@$methodName incoming query: $callingUid => $targetApp" } + if (HMAServiceCache.instance.shouldHideFromUid(callingUid, targetApp) == true) { + applyReturnValue() + service.increasePMFilterCount(callingUid) + logD(TAG) { "@$methodName caller cache: $callingUid, target: $targetApp" } + return + } + val callingApps = findCallingApps(callingUid) + val caller = callingApps?.firstOrNull { service.shouldHide(it, targetApp) } + if (caller != null) { + logD(TAG) { "@$methodName caller: $callingUid $caller, target: $targetApp" } + applyReturnValue() + val last = lastFilteredApp.getAndSet(caller) + if (last != caller) logI(TAG) { "@${methodName}: query from $caller" } + HMAServiceCache.instance.putShouldHideUidCache(callingUid, caller, targetApp) + service.increasePMFilterCount(caller) + } + } + + fun applyInstallerHiding( + methodName: String, + findCallingUid: () -> Int?, + findTargetApp: () -> String?, + applyReturnValue: (Int) -> Unit, + ) { + val callingUid = findCallingUid() ?: return + if (callingUid == Constants.UID_SYSTEM) return + + val callingApps = getCallingApps(service, callingUid) + val callingHandle = UserHandle.getUserHandleForUid(callingUid) + + val query = findTargetApp() ?: return + + for (caller in callingApps) { + val isHide = service.shouldHideInstallationSource(caller, query, callingHandle) + if (isHide == Constants.FAKE_INSTALLATION_SOURCE_DISABLED) continue + + logD(TAG) { "@$methodName: Applied installer hiding for $caller - $callingUid => $isHide" } + + applyReturnValue(isHide) + + service.increaseInstallerFilterCount(caller) + break + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsPackageEventsHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsPackageEventsHook.kt new file mode 100644 index 000000000..9c6a3bbb6 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/PmsPackageEventsHook.kt @@ -0,0 +1,56 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.PACKAGE_MANAGER_SERVICE_CLASS + +class PmsPackageEventsHook(private val service: HMAService) : IFrameworkHook { + override val TAG = "PmsPackageEventsHook" + + override fun load() { + logI(TAG) { "Load hook" } + + BulkHooker.instance.apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + hookBefore( + "com.android.server.pm.BroadcastHelper", + "sendPackageBroadcastAndNotify", + ) { param -> + service.handlePackageEvent( + param.getArgument(1) as String?, + param.getArgument(2) as String?, + param.getArgument(3) as Bundle?, + ) + } + + hookBefore( + "com.android.internal.content.PackageMonitor", + "onReceive", + ) { param -> + val intent = param.getArgument(2) as? Intent? ?: return@hookBefore + + service.handlePackageEvent( + intent.action, + intent.data?.encodedSchemeSpecificPart, + intent.extras, + ) + } + } else { + hookBefore( + PACKAGE_MANAGER_SERVICE_CLASS, + "sendPackageBroadcast", + ) { param -> + service.handlePackageEvent( + param.getArgument(1) as String?, + param.getArgument(2) as String?, + param.getArgument(3) as Bundle?, + ) + } + } + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ZygoteHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ZygoteHook.kt new file mode 100644 index 000000000..3a9759827 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/hook/ZygoteHook.kt @@ -0,0 +1,38 @@ +package org.frknkrc44.hma_oss.zygote.hook + +import icu.nullptr.hidemyapplist.common.CollectionUtils.lastOrNullWithType +import icu.nullptr.hidemyapplist.common.Constants +import org.frknkrc44.hma_oss.zygote.service.BulkHooker +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.ZygoteConstants.ZYGOTE_PROCESS_CLASS + +class ZygoteHook(private val service: HMAService) : IFrameworkHook { + override val TAG = "ZygoteHook" + + override fun load() { + BulkHooker.instance.hookBefore( + ZYGOTE_PROCESS_CLASS, + "start", + ) { param -> + logD(TAG) { "@startZygoteProcess: Starting ${param.args.contentToString()}" } + + // ignore if the GIDs array is null + val gIDsIndex = param.args.indexOfFirst { it is IntArray } + if (gIDsIndex < 0) return@hookBefore + + val caller = param.args.lastOrNullWithType() ?: return@hookBefore + var perms = service.getRestrictedZygotePermissions(caller) ?: return@hookBefore + if (perms.isNotEmpty()) { + val gIDs = param.args[gIDsIndex] as IntArray + + // add more security, reject if not available in GID_PAIRS + perms = perms.filter { Constants.GID_PAIRS.containsValue(it) } + + logD(TAG) { "@startZygoteProcess: GIDs are ${gIDs.contentToString()}, removing $perms now" } + param.setArgument(gIDsIndex, gIDs.filter { it !in perms }.toIntArray()) + service.increaseOthersFilterCount(caller) + } + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHooker.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHooker.kt new file mode 100644 index 000000000..67fa61e0a --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHooker.kt @@ -0,0 +1,272 @@ +package org.frknkrc44.hma_oss.zygote.service + +import android.os.Build +import com.v7878.unsafe.ArtMethodUtils +import com.v7878.unsafe.Reflection +import com.v7878.unsafe.invoke.EmulatedStackFrame +import com.v7878.unsafe.invoke.EmulatedStackFrame.RETURN_VALUE_IDX +import com.v7878.unsafe.invoke.Transformers +import com.v7878.vmtools.HookTransformer +import com.v7878.vmtools.Hooks +import org.frknkrc44.hma_oss.zygote.ZygoteEntry +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils +import org.frknkrc44.hma_oss.zygote.util.ZLUtils +import java.lang.invoke.MethodHandle +import java.lang.reflect.Executable +import java.lang.reflect.Method + +class BulkHooker private constructor() { + companion object { + val instance: BulkHooker by lazy { BulkHooker() } + const val PARAMETER_COUNT_UNKNOWN = -1 + } + + internal val hooks: MutableMap> = HashMap() + + private fun addHook(clazz: String, methodName: String, hookOnce: Boolean, paramCount: Int, impl: HookTransformer) { + val inDisabledHooks = HMAService.instance?.config?.disabledHooks?.any { + clazz == it.className && + methodName == it.methodName && + paramCount == it.argumentCount + } + + if (inDisabledHooks == true) { + logI(ZygoteEntry.TAG) { "Disabled hook: $clazz -> $methodName($paramCount)" } + return + } + + val element = HookElement( + impl = impl, + methodName = methodName, + hookOnce = hookOnce, + paramCount = paramCount, + ) + + if (applyHook(clazz, element)) { + if (clazz !in hooks) { + hooks[clazz] = mutableListOf() + } + + hooks[clazz]!!.add(element) + } else { + logI(ZygoteEntry.TAG) { "Invalid hook removed: $clazz -> $methodName($paramCount)" } + } + } + + internal fun hookBefore( + clazz: String, + methodName: String, + hookOnce: Boolean = true, + paramCount: Int = PARAMETER_COUNT_UNKNOWN, + hook: (param: HookParam) -> Unit, + ) { + addHook(clazz, methodName, hookOnce, paramCount) { original, frame -> + val value = ReturnValue() + + try { + hook(HookParam(clazz, original, frame, methodName, value)) + } catch (it: Throwable) { + logE(ZygoteEntry.TAG, it) { it.message ?: "Unknown error on hook" } + } + + if (!value.replace) { + try { + invokeExactCompat(clazz, methodName, original, frame, value) + } catch (it: Throwable) { + logD(ZygoteEntry.TAG, it) { it.message ?: "Unknown error on original function" } + value.throwable = it + } + } + + value.throwable?.let { + ServiceUtils.clearStackTraces(it) + + throw it + } + + if (value.replace) { + ZLUtils.setReturnValue(frame, value.result) + } + } + } + + internal fun hookAfter( + clazz: String, + methodName: String, + hookOnce: Boolean = true, + paramCount: Int = PARAMETER_COUNT_UNKNOWN, + hook: (param: HookParam) -> Unit, + ) { + addHook(clazz, methodName, hookOnce, paramCount) { original, frame -> + val value = ReturnValue() + + try { + invokeExactCompat(clazz, methodName, original, frame, value) + } catch (it: Throwable) { + logD(ZygoteEntry.TAG, it) { it.message ?: "Unknown error on original function" } + value.throwable = it + } + + if (value.throwable == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + value.result = frame.accessor().getValue(RETURN_VALUE_IDX) + } + + try { + hook(HookParam(clazz, original, frame, methodName, value)) + } catch (it: Throwable) { + logE(ZygoteEntry.TAG, it) { it.message ?: "Unknown error on hook" } + } + + value.throwable?.let { + ServiceUtils.clearStackTraces(it) + + throw it + } + + ZLUtils.setReturnValue(frame, value.result) + } + } + + internal fun applyHook( + clazz: String, + element: HookElement, + loader: ClassLoader? = SystemServerHook.classLoader, + ): Boolean { + var curClazz: Class<*>? + try { + curClazz = Class.forName(clazz, true, loader) + } catch (ex: ClassNotFoundException) { + logE(ZygoteEntry.TAG, ex) { "Class $clazz not found" } + return false + } + + fun applyForClass(clazz: Class<*>?) { + val executables = Reflection.getHiddenExecutables(clazz).filter { executable -> + element.methodName == executable.name && + (element.paramCount in listOf(PARAMETER_COUNT_UNKNOWN, executable.parameterCount)) + }.sortedWith { v1, v2 -> + v1.parameterCount.compareTo(v2.parameterCount) + } + + for (executable in executables) { + if (!element.hookFinished) { + logD(ZygoteEntry.TAG) { "Hooked: $executable" } + + val memoryAddresses = Hooks.hook( + executable, Hooks.EntryPointType.DIRECT, + element.impl, Hooks.EntryPointType.DIRECT + ) + + logV(ZygoteEntry.TAG) { "Memory address map: $memoryAddresses" } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + element.memoryAddresses = memoryAddresses + element.method = executable + } + + element.applyCount++ + + if (element.hookOnce) { + element.hookFinished = true + break + } + } + } + } + + while ( + !element.hookFinished && + curClazz != null && + curClazz.javaClass.simpleName != "Object" + ) { + applyForClass(curClazz) + curClazz = curClazz.superclass + } + + if (!element.hookOnce && element.applyCount >= 1) { + element.hookFinished = true + } + + return element.hookFinished + } + + fun invokeExactCompat(clazz: String, methodName: String, original: MethodHandle, frame: EmulatedStackFrame, value: ReturnValue) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + val element = findHookElement(clazz, methodName)!! + + ArtMethodUtils.setExecutableEntryPoint( + element.method!!, + element.memoryAddresses?.second!! + ) + + val thisObject = ZLUtils.getArgument(frame, 0) + val args = ZLUtils.dumpArgs(frame, true) + + value.result = (element.method as Method).invoke(thisObject, *args) + + ArtMethodUtils.setExecutableEntryPoint( + element.method!!, + element.memoryAddresses?.first!! + ) + } else { + Transformers.invokeExactNoChecks(original, frame) + } + } + + fun findHookElement(clazz: String, methodName: String): HookElement? { + hooks[clazz]?.forEach { element -> + if (element.methodName == methodName) { + return element + } + } + + return null + } + + fun findAltMethod( + clazzNames: List, + methodNames: List, + paramCount: Int = -1, + loader: ClassLoader? = SystemServerHook.classLoader, + ): Executable? { + for (clazz in clazzNames) { + var curClazz: Class<*>? + try { + curClazz = Class.forName(clazz, true, loader) + } catch (ex: ClassNotFoundException) { + logE(ZygoteEntry.TAG, ex) { "Class $clazz not found" } + return null + } + + fun findMethods(clazz: Class<*>): List { + return Reflection.getHiddenExecutables(clazz).filter { executable -> + executable.name in methodNames && + (paramCount in listOf(PARAMETER_COUNT_UNKNOWN, executable.parameterCount)) + }.sortedWith { v1, v2 -> + v1.parameterCount.compareTo(v2.parameterCount) + } + } + + var methods = listOf() + + while ( + methods.isEmpty() && + curClazz != null && + curClazz.javaClass.simpleName != "Object" + ) { + methods = findMethods(curClazz) + curClazz = curClazz.superclass + } + + return methods.firstOrNull() + } + + logI(ZygoteEntry.TAG) { "Invalid hook detected: $clazzNames -> $methodNames($paramCount)" } + + return null + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHookerData.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHookerData.kt new file mode 100644 index 000000000..47b25f7e3 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/BulkHookerData.kt @@ -0,0 +1,68 @@ +package org.frknkrc44.hma_oss.zygote.service + +import android.util.Pair +import com.v7878.unsafe.invoke.EmulatedStackFrame +import com.v7878.vmtools.HookTransformer +import org.frknkrc44.hma_oss.zygote.util.ZLUtils +import java.lang.invoke.MethodHandle +import java.lang.reflect.Executable + +class ReturnValue(initialValue: Any? = null) { + var replace: Boolean = false + private set + + var result: Any? = initialValue + set(newValue) { + field = newValue + replace = true + } + + var throwable: Throwable? = null +} + +data class HookParam( + val clazz: String, + val original: MethodHandle, + val frame: EmulatedStackFrame, + val methodName: String, + val returnValue: ReturnValue, +) { + var result: Any? + get() = returnValue.result + set(newValue) { returnValue.result = newValue } + + /** + * @return Class of the return type + */ + val returnType: Class<*> get() = frame.type().returnType() + + /** + * Returns the first argument + */ + val thisObject by lazy { ZLUtils.getArgument(frame, 0) } + + fun getArgument(index: Int) = ZLUtils.getArgument(frame, index) + + fun setArgument(index: Int, value: Any) = ZLUtils.setArgument(frame, index, value) + + /** + * - `args[0] == thisObject` + * - `args[1:] == function args` + */ + val args by lazy { ZLUtils.dumpArgs(frame) } + + var throwable: Throwable? + get() = returnValue.throwable + set(newValue) { returnValue.throwable = newValue } +} + +data class HookElement( + val impl: HookTransformer, + val methodName: String, + val hookOnce: Boolean, + var method: Executable? = null, + var memoryAddresses: Pair? = null, + var hookFinished: Boolean = false, + val paramCount: Int = -1, + var applyCount: Int = 0, +) diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAService.kt similarity index 85% rename from xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt rename to zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAService.kt index a7b5705ca..85a5da4cd 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAService.kt @@ -1,4 +1,4 @@ -package icu.nullptr.hidemyapplist.xposed +package org.frknkrc44.hma_oss.zygote.service import android.content.Intent import android.content.pm.ApplicationInfo @@ -11,9 +11,8 @@ import android.os.RemoteException import android.os.UserHandle import android.provider.Settings import android.util.Log -import com.github.kyuubiran.ezxhelper.utils.isStatic -import com.github.kyuubiran.ezxhelper.utils.removeIf import icu.nullptr.hidemyapplist.common.AppPresets +import icu.nullptr.hidemyapplist.common.CollectionUtils.removeIf import icu.nullptr.hidemyapplist.common.Constants import icu.nullptr.hidemyapplist.common.Constants.PARCEL_TYPE_CONFIG import icu.nullptr.hidemyapplist.common.Constants.PARCEL_TYPE_LOG @@ -23,39 +22,41 @@ import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.RiskyPackageUtils.appHasGMSConnection import icu.nullptr.hidemyapplist.common.SettingsPresets import icu.nullptr.hidemyapplist.common.Utils.binderLocalScope +import icu.nullptr.hidemyapplist.common.Utils.cleanRemnantsFromConfig import icu.nullptr.hidemyapplist.common.Utils.generateRandomString import icu.nullptr.hidemyapplist.common.Utils.getInstalledApplicationsCompat import icu.nullptr.hidemyapplist.common.Utils.getPackageInfoCompat import icu.nullptr.hidemyapplist.common.Utils.getPackageUidCompat import icu.nullptr.hidemyapplist.common.app_presets.DetectorAppsPreset import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI -import icu.nullptr.hidemyapplist.xposed.Logcat.logW -import icu.nullptr.hidemyapplist.xposed.Logcat.logWithLevel -import icu.nullptr.hidemyapplist.xposed.hook.AccessibilityHook -import icu.nullptr.hidemyapplist.xposed.hook.ActivityHook -import icu.nullptr.hidemyapplist.xposed.hook.AppDataIsolationHook -import icu.nullptr.hidemyapplist.xposed.hook.ContentProviderHook -import icu.nullptr.hidemyapplist.xposed.hook.IFrameworkHook -import icu.nullptr.hidemyapplist.xposed.hook.ImmHook -import icu.nullptr.hidemyapplist.xposed.hook.PlatformCompatHook -import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget29 -import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget30 -import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget31 -import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget33 -import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget34 -import icu.nullptr.hidemyapplist.xposed.hook.PmsPackageEventsHook -import icu.nullptr.hidemyapplist.xposed.hook.ZygoteHook import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.hook.AccessibilityHook +import org.frknkrc44.hma_oss.zygote.hook.ActivityHook +import org.frknkrc44.hma_oss.zygote.hook.AppDataIsolationHook +import org.frknkrc44.hma_oss.zygote.hook.ContentProviderHook +import org.frknkrc44.hma_oss.zygote.hook.IFrameworkHook +import org.frknkrc44.hma_oss.zygote.hook.ImmHook +import org.frknkrc44.hma_oss.zygote.hook.PlatformCompatHook +import org.frknkrc44.hma_oss.zygote.hook.PmsHookTarget29 +import org.frknkrc44.hma_oss.zygote.hook.PmsHookTarget30 +import org.frknkrc44.hma_oss.zygote.hook.PmsHookTarget31 +import org.frknkrc44.hma_oss.zygote.hook.PmsHookTarget33 +import org.frknkrc44.hma_oss.zygote.hook.PmsHookTarget34 +import org.frknkrc44.hma_oss.zygote.hook.PmsPackageEventsHook +import org.frknkrc44.hma_oss.zygote.hook.ZygoteHook +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logW +import org.frknkrc44.hma_oss.zygote.util.Logcat.logWithLevel +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.findAndVerifyAppSignature import rikka.hidden.compat.ActivityManagerApis import rikka.hidden.compat.UserManagerApis import java.io.File import java.io.FileInputStream +import java.lang.reflect.Modifier import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -import kotlin.concurrent.thread class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { @@ -79,7 +80,8 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { val systemApps = mutableSetOf() private val frameworkHooks = mutableSetOf() val executor: ExecutorService = Executors.newSingleThreadExecutor() - private val uidHideCache = mutableListOf>>() + internal var appUid = 0 + private set var config = JsonConfig().apply { detailLog = true } private set @@ -100,10 +102,9 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { AppPresets.instance.loggerFunction = { level, msg -> logWithLevel(level, "AppPresets") { msg } } + reloadPresetsFromScratch() - thread { - reloadPresetsFromScratch() - } + appUid = findAndVerifyAppSignature(pms) } private fun searchDataDir() { @@ -128,7 +129,10 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { } } if (!this::dataDir.isInitialized) { - dataDir = "/data/misc/hide_my_applist_" + generateRandomString(16) + dataDir = "/data/misc/hide_my_applist_" + generateRandomString( + 16, + ('a'..'z').toList() + ) } File("$dataDir/log").mkdirs() @@ -178,7 +182,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { logW(TAG) { "Config version mismatch, need to reload" } return } - cleanRemnants(loading) + cleanRemnantsFromConfig(loading) config = loading logI(TAG) { "Config loaded" } } @@ -199,15 +203,6 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { logI(TAG) { "Filter counts loaded" } } - private fun cleanRemnants(config: JsonConfig) { - for (app in config.scope.values) { - app.applyTemplates.removeIf { !config.templates.containsKey(it) } - app.applyPresets.removeIf { !AppPresets.instance.presetNames.contains(it) } - app.applySettingTemplates.removeIf { !config.settingsTemplates.containsKey(it) } - app.applySettingsPresets.removeIf { !SettingsPresets.instance.presetNames.contains(it) } - } - } - private fun installHooks() { getInstalledApplicationsCompat(pms, PackageManager.MATCH_ALL.toLong(), 0) .mapNotNullTo(systemApps) { appInfo -> @@ -215,7 +210,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0 if (isSystemApp) appInfo.packageName else null - } + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { frameworkHooks.add(PmsHookTarget34(this)) @@ -253,10 +248,6 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { caller, amount, FilterHolder.FilterType.PACKAGE_MANAGER ) - fun increaseALFilterCount(callingUid: Int?, amount: Int = 1) = increaseFilterCount( - callingUid, amount, FilterHolder.FilterType.ACTIVITY_LAUNCH - ) - fun increaseALFilterCount(caller: String?, amount: Int = 1) = increaseFilterCount( caller, amount, FilterHolder.FilterType.ACTIVITY_LAUNCH ) @@ -276,8 +267,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { fun increaseFilterCount(uid: Int?, amount: Int = 1, filterType: FilterHolder.FilterType) { if (uid == null || amount < 1) return - val caller = uidHideCache.firstOrNull { it.first == uid }?.second - if (caller == null) return + val caller = HMAServiceCache.instance.findCallerByUid(uid) ?: return return increaseFilterCount(caller, amount, filterType) } @@ -305,11 +295,12 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { fun isHookEnabled(packageName: String?) = config.scope.containsKey(packageName) - fun isAppDataIsolationExcluded(packageName: String?): Boolean { - if (packageName.isNullOrBlank()) return false + fun isAnySettingsReplacementsEnabled(packageName: String?) = config.scope[packageName]?.let { + it.applySettingsPresets.isNotEmpty() || it.applySettingTemplates.isNotEmpty() + } ?: false - return config.scope[packageName]?.excludeVoldIsolation ?: false - } + fun isAppDataIsolationExcluded(packageName: String?) = + config.scope[packageName]?.excludeVoldIsolation ?: false fun getSpoofedSetting(caller: String?, name: String?, database: String): ReplacementItem? { if (caller == null || name == null) return null @@ -333,33 +324,16 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { } fun getEnabledSettingsTemplates(caller: String?): Set { - if (caller == null) return setOf() return config.scope[caller]?.applySettingTemplates ?: return setOf() } fun getEnabledSettingsPresets(caller: String?): Set { - if (caller == null) return setOf() return config.scope[caller]?.applySettingsPresets ?: return setOf() } fun isAppInGMSIgnoredPackages(caller: String, query: String) = (caller in Constants.gmsPackages) && appHasGMSConnection(query) - fun shouldHideFromUid(uid: Int, query: String?): Boolean? { - if (query == null) return null - - return uidHideCache.any { it.first == uid && it.third.contains(query) } - } - - fun putShouldHideUidCache(uid: Int, caller: String, query: String) { - val findList = uidHideCache.firstOrNull { it.first == uid } - if (findList != null) { - findList.third.add(query) - } else { - uidHideCache.add(Triple(uid, caller, mutableListOf(query))) - } - } - fun shouldHide(caller: String?, query: String?): Boolean { if (caller == null || query == null) return false if (caller == BuildConfig.APP_PACKAGE_NAME) return false @@ -444,20 +418,10 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { } override fun stopService(cleanEnv: Boolean) { - logI(TAG) { "Stop service" } - synchronized(loggerLock) { - logcatAvailable = false - } - synchronized(configLock) { - frameworkHooks.forEach(IFrameworkHook::unload) - frameworkHooks.clear() - if (cleanEnv) { - logI(TAG) { "Clean runtime environment" } - File(dataDir).deleteRecursively() - return - } - } - instance = null + if (!cleanEnv) return + + logI(TAG) { "Clean runtime environment" } + File(dataDir).deleteRecursively() } fun addLog(parsedMsg: String) { @@ -472,7 +436,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { synchronized(configLock) { runCatching { val newConfig = JsonConfig.parse(json) - cleanRemnants(newConfig) + cleanRemnantsFromConfig(newConfig) if (newConfig.configVersion != BuildConfig.CONFIG_VERSION) { logW(TAG) { "Sync config: version mismatch, need reboot" } return @@ -480,7 +444,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { config = newConfig configFile.writeText(json) frameworkHooks.forEach(IFrameworkHook::onConfigChanged) - uidHideCache.clear() + HMAServiceCache.instance.clearUidCache() // remove filter counts for apps if they are not in config filterHolder.filterCounts.removeIf { key, _ -> !config.scope.containsKey(key) } @@ -532,6 +496,10 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { AppPresets.instance.apply { when (eventType) { Intent.ACTION_PACKAGE_ADDED -> { + if (packageName == BuildConfig.APP_PACKAGE_NAME && appUid < 0) { + appUid = findAndVerifyAppSignature(pms) + } + handlePackageAdded(pms, packageName) } Intent.ACTION_PACKAGE_REMOVED -> { @@ -540,6 +508,12 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { return } + if (packageName == BuildConfig.APP_PACKAGE_NAME && appUid >= 0) { + logI(TAG) { "The manager app is uninstalled, looking for alternatives" } + + appUid = findAndVerifyAppSignature(pms) + } + handlePackageRemoved(packageName) } } @@ -585,7 +559,7 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { } val readableVariables = settingClass.declaredFields.mapNotNull { field -> - if (field.isStatic && field.type.simpleName == "String") field.get(null) as String else null + if (Modifier.isStatic(field.modifiers) && field.type.simpleName == "String") field.get(null) as String else null } return readableVariables.sorted().toTypedArray() @@ -618,6 +592,24 @@ class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { override fun getServiceVersionName() = BuildConfig.APP_VERSION_NAME + override fun getLoadedHooks(): Array { + val hookList = mutableListOf() + + for ((className, hookElements) in BulkHooker.instance.hooks) { + for (element in hookElements) { + hookList.add( + JsonConfig.HookItem( + className, + element.methodName, + element.paramCount, + ).toString() + ) + } + } + + return hookList.toTypedArray() + } + override fun readFD(type: Int): ParcelFileDescriptor { return when (type) { PARCEL_TYPE_LOG -> { diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAServiceCache.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAServiceCache.kt new file mode 100644 index 000000000..9c1bf59e4 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/HMAServiceCache.kt @@ -0,0 +1,28 @@ +package org.frknkrc44.hma_oss.zygote.service + +class HMAServiceCache private constructor() { + companion object { + val instance by lazy { HMAServiceCache() } + } + + private val uidHideCache = mutableListOf>>() + + fun findCallerByUid(uid: Int) = uidHideCache.firstOrNull { it.first == uid }?.second + + fun shouldHideFromUid(uid: Int, query: String?): Boolean? { + if (query == null) return null + + return uidHideCache.firstOrNull { it.first == uid && it.third.contains(query) } != null + } + + fun putShouldHideUidCache(uid: Int, caller: String, query: String) { + val findList = uidHideCache.firstOrNull { it.first == uid } + if (findList != null) { + findList.third.add(query) + } else { + uidHideCache.add(Triple(uid, caller, mutableListOf(query))) + } + } + + fun clearUidCache() = uidHideCache.clear() +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/SystemServerHook.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/SystemServerHook.kt new file mode 100644 index 000000000..db695f53a --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/SystemServerHook.kt @@ -0,0 +1,96 @@ +package org.frknkrc44.hma_oss.zygote.service + +import android.annotation.SuppressLint +import android.content.pm.IPackageManager +import android.os.Build +import com.v7878.r8.annotations.DoNotShrink +import com.v7878.unsafe.Reflection.getDeclaredMethod +import com.v7878.unsafe.invoke.Transformers +import com.v7878.vmtools.Hooks +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.waitForService +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.callStaticMethod +import kotlin.concurrent.thread + +@SuppressLint("PrivateApi") +object SystemServerHook { + private const val TAG = "SystemServerHook" + private const val SYSTEM_SERVER: String = "com.android.server.SystemServer" + private const val RUNTIME_INIT: String = "com.android.internal.os.RuntimeInit" + private const val ZYGOTE_INIT: String = "com.android.internal.os.ZygoteInit" + + var classLoader: ClassLoader? = null + var initialized = false + + @Throws(Throwable::class) + fun onSystemServer(loader: ClassLoader?) { + assert(loader != null) { "Class loader is null, aborting!" } + + logV(TAG) { "Class loader found: $loader" } + + classLoader = loader + + if (!initialized) { + initialized = true + + thread { + val pms = waitForService("package") as IPackageManager + val pmn = waitForService("package_native") + logD(TAG) { "Got pms: $pms, $pmn" } + + runCatching { + UserService.register(pms, pmn) + logI(TAG) { "User service started" } + }.onFailure { + logE(TAG, it) { "System service crashed" } + } + } + } + } + + @DoNotShrink + @Throws(Throwable::class) + @JvmStatic + fun init() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + logI(TAG) { "Trying to invoke 12+ mode" } + + runCatching { + val loader = callStaticMethod( + Class.forName(ZYGOTE_INIT), + "getOrCreateSystemServerClassLoader" + ) + + onSystemServer(loader as? ClassLoader) + }.onSuccess { + return + }.onFailure { + logE(TAG, it) { "An exception occurred while trying 12+ mode" } + // falls back to 11- mode + } + } + + logI(TAG) { "Trying to invoke 11- mode" } + + val method = getDeclaredMethod( + Class.forName(RUNTIME_INIT), "findStaticMain", + String::class.java, Array::class.java, ClassLoader::class.java + ) + + Hooks.hook(method, Hooks.EntryPointType.CURRENT, { original, frame -> + try { + val accessor = frame.accessor() + if (SYSTEM_SERVER == accessor.getReference(0)) { + val loader: ClassLoader? = accessor.getReference(2) + onSystemServer(loader) + } + } catch (th: Throwable) { + logE(TAG, th) { "An exception occurred while checkSystemServer" } + } + Transformers.invokeExact(original, frame) + }, Hooks.EntryPointType.DIRECT) + } +} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/UserService.kt similarity index 52% rename from xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt rename to zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/UserService.kt index 4dd21fa78..4acf531b7 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/service/UserService.kt @@ -1,18 +1,16 @@ -package icu.nullptr.hidemyapplist.xposed +package org.frknkrc44.hma_oss.zygote.service -import android.app.ActivityManagerHidden import android.content.AttributionSource import android.content.pm.IPackageManager import android.os.Build import android.os.Bundle -import android.os.ServiceManager -import de.robv.android.xposed.XposedHelpers import icu.nullptr.hidemyapplist.common.Constants -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.xposed.Logcat.logD -import icu.nullptr.hidemyapplist.xposed.Logcat.logE -import icu.nullptr.hidemyapplist.xposed.Logcat.logI import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.util.Logcat.logD +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.ServiceUtils.waitForService +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.getStaticIntField import rikka.hidden.compat.ActivityManagerApis import rikka.hidden.compat.adapter.UidObserverAdapter @@ -20,18 +18,20 @@ object UserService { private const val TAG = "HMA-UserService" - private var appUid = 0 + private val managerAppUid get() = HMAService.instance?.appUid ?: -1 private val uidObserver = object : UidObserverAdapter() { override fun onUidActive(uid: Int) { - if (HMAService.instance == null) { - logE(TAG) { "HMAService instance is not available, maybe stopped" } + if (managerAppUid < 0 || uid != managerAppUid) { return } - if (uid != appUid) return try { - val provider = ActivityManagerApis.getContentProviderExternal(Constants.PROVIDER_AUTHORITY, 0, null, null) + val userId = uid / 100000 + + logD(TAG) { "Calculated user id: $userId" } + + val provider = ActivityManagerApis.getContentProviderExternal(Constants.PROVIDER_AUTHORITY, userId, null, null) assert (provider != null) { "Failed to get provider" } @@ -58,48 +58,23 @@ object UserService { fun register(pms: IPackageManager, pmn: Any?) { logI(TAG) { "Initialize HMAService - Version ${BuildConfig.APP_VERSION_NAME}" } - val service = HMAService(pms, pmn) - try { - appUid = Utils.getPackageUidCompat(service.pms, BuildConfig.APP_PACKAGE_NAME, 0, 0) - assert(appUid >= 0) { - "App UID cannot be -1 or lower" - } - } catch (e: Throwable) { - logE(TAG, e) { "Fatal: Cannot get package details\nCompile this app from source with your changes" } - return - } - - logD(TAG) { "Client uid: $appUid" } - - waitActivityService() + waitForService("activity") ActivityManagerApis.registerUidObserver( uidObserver, - ActivityManagerHidden.UID_OBSERVER_ACTIVE, - ActivityManagerHidden.PROCESS_STATE_TOP, + getActMgrField("UID_OBSERVER_ACTIVE"), + getActMgrField("PROCESS_STATE_TOP"), null ) logI(TAG) { "Registered observer" } - } - private fun waitActivityService() { - // use the new Android method for 11+ - // but use the getService fallback if fails to run - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - try { - XposedHelpers.callStaticMethod( - ServiceManager::class.java, - "waitForService", - "activity" - ) - - return - } catch (_: Throwable) {} - } - - while (ServiceManager.getService("activity") == null) { - Thread.sleep(250) - } + // no need to put in a variable + HMAService(pms, pmn) } + + private fun getActMgrField(name: String) = getStaticIntField( + "android.app.ActivityManager", + name, + ) } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Logcat.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/Logcat.kt similarity index 87% rename from xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Logcat.kt rename to zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/Logcat.kt index ba3604550..53e878ddb 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Logcat.kt +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/Logcat.kt @@ -1,9 +1,9 @@ -package icu.nullptr.hidemyapplist.xposed +package org.frknkrc44.hma_oss.zygote.util import android.os.SystemProperties import android.util.Log -import de.robv.android.xposed.XposedBridge import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.service.HMAService import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -22,6 +22,12 @@ object Logcat { fun logE(tag: String, cause: Throwable? = null, msg: () -> String) = logWithLevel(Log.ERROR, tag, cause, msg) + @JvmStatic + fun logILegacy(tag: String, msg: String, cause: Throwable?) = logI(tag, cause) { msg } + + @JvmStatic + fun logELegacy(tag: String, msg: String, cause: Throwable?) = logE(tag, cause) { msg } + fun logWithLevel(level: Int, tag: String, cause: Throwable? = null, msg: () -> String) { if (level != Log.ERROR && HMAService.instance?.config?.errorOnlyLog == true) return if (level <= Log.DEBUG && HMAService.instance?.config?.detailLog == false) return @@ -61,6 +67,6 @@ object Logcat { if (logdReady != true) return - XposedBridge.log(msg) + Log.i("HMA-OSS", msg) } } diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ServiceUtils.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ServiceUtils.kt new file mode 100644 index 000000000..95c57fe28 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ServiceUtils.kt @@ -0,0 +1,172 @@ +package org.frknkrc44.hma_oss.zygote.util + +import android.app.ActivityThread +import android.content.Context.USER_SERVICE +import android.content.pm.IPackageManager +import android.os.Binder +import android.os.Build +import android.os.IBinder +import android.os.IUserManager +import android.os.ServiceManager +import com.android.apksig.ApkVerifier +import com.v7878.unsafe.Reflection.getDeclaredMethod +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.Utils +import icu.nullptr.hidemyapplist.common.Utils.getPackageInfoCompat +import org.frknkrc44.hma_oss.common.BuildConfig +import org.frknkrc44.hma_oss.zygote.Magic +import org.frknkrc44.hma_oss.zygote.service.HMAService +import org.frknkrc44.hma_oss.zygote.util.Logcat.logE +import org.frknkrc44.hma_oss.zygote.util.Logcat.logI +import org.frknkrc44.hma_oss.zygote.util.Logcat.logV +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.callMethod +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.callMethodWithTypes +import org.frknkrc44.hma_oss.zygote.util.ZLUtils.findField +import rikka.hidden.compat.UserManagerApis +import java.io.File + + +object ServiceUtils { + const val TAG = "ServiceUtils" + + @Throws(InterruptedException::class) + fun waitForService(name: String?): IBinder? { + try { + return getDeclaredMethod( + ServiceManager::class.java, + "waitForService", + String::class.java, + ).invoke(null, name) as IBinder? + } catch (e: Throwable) { + logE(TAG, e) { "An error occurred on waitForService" } + } + + var service: IBinder? = null + + do { + Thread.sleep(250) + } while ((ServiceManager.getService(name).also { service = it }) == null) + + return service + } + + fun getPackageNameFromPackageSettings(packageSettings: Any?): String? { + if (packageSettings == null) return null + + return try { + callMethod(packageSettings, "getPackageName") as String? + } catch (_: Throwable) { + runCatching { + findField( + packageSettings::class.java, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) "mName" else "name" + )?.apply { isAccessible = true }?.get(packageSettings) as? String + }.getOrNull() + } + } + + fun getPackageManager() = ActivityThread.currentActivityThread().application.packageManager!! + + fun getCallingApps(service: HMAService): Array { + return getCallingApps(service, Binder.getCallingUid()) + } + + fun getCallingApps(service: HMAService, callingUid: Int): Array { + if (callingUid == Constants.UID_SYSTEM) return arrayOf() + return Utils.binderLocalScope { + service.pms.getPackagesForUid(callingUid) + } ?: arrayOf() + } + + fun findAndVerifyAppSignature(pms: IPackageManager): Int { + val userService = waitForService(USER_SERVICE) + + try { + val userManager = IUserManager.Stub.asInterface(userService) + val profiles = mutableSetOf().also { set -> + val userIds = UserManagerApis.getUserIdsNoThrow() + + runCatching { + userIds.forEach { + val profiles = callMethodWithTypes( + userManager, + "getProfileIds", + arrayOf(Int::class.javaPrimitiveType!!, Boolean::class.javaPrimitiveType!!), + arrayOf(it, false), + ) ?: return@forEach + + (profiles as IntArray).forEach { pId -> set.add(pId) } + } + }.onFailure { + set.addAll(userIds) + } + } + + for (uid in profiles) { + logV(TAG) { "@findAndVerifyAppSignature: checking for uid $uid" } + + val pkgInfo = runCatching { + getPackageInfoCompat(pms, BuildConfig.APP_PACKAGE_NAME, 0L, uid) + }.getOrNull() + + if (pkgInfo != null) { + if (verifyAppSignature(pkgInfo.applicationInfo?.sourceDir)) { + val appUid = pkgInfo.applicationInfo!!.uid + + logI(TAG) { "The manager app signature is verified successfully, uid: $appUid" } + + return appUid + } else { + throw AssertionError("The manager app is modified, skipping") + } + } + } + } catch (e: Throwable) { + logE(TAG, e) { "Fatal: Cannot get package details\nCompile this app from source with your changes" } + + return -1 + } + + logE(TAG) { "The manager app is not found, skipping" } + + return -1 + } + + private fun verifyAppSignature(path: String?): Boolean { + if (path == null) return false + + val verifier = ApkVerifier.Builder(File(path)) + .setMinCheckedPlatformVersion(24) + .build() + val result = verifier.verify() + if (!result.isVerified) return false + val mainCert = result.signerCertificates[0] + return mainCert.encoded.contentEquals(Magic.magicNumbers) + } + + fun clearStackTraces(throwableIn: Throwable) { + var throwable: Throwable? = throwableIn + + while (throwable != null) { + val newTrace = throwable.stackTrace.filter { item -> + !Utils.containsMultiple( + item.className, + "BulkHooker", + "com.v7878", + "MethodHandle", + BuildConfig.APP_PACKAGE_NAME, + ) && !Utils.containsMultiple( + item.fileName, + "r8-map-id-", + "dex-id-", + ) + } + + if (newTrace.size != throwable.stackTrace.size) { + throwable.stackTrace = newTrace.toTypedArray() + } + + throwable = throwable.cause + } + } +} diff --git a/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZLUtils.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZLUtils.kt new file mode 100644 index 000000000..96ce5c267 --- /dev/null +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZLUtils.kt @@ -0,0 +1,132 @@ +package org.frknkrc44.hma_oss.zygote.util + +import com.v7878.unsafe.Reflection.getDeclaredField +import com.v7878.unsafe.Reflection.getDeclaredMethod +import com.v7878.unsafe.invoke.EmulatedStackFrame +import com.v7878.unsafe.invoke.EmulatedStackFrame.RETURN_VALUE_IDX +import org.frknkrc44.hma_oss.zygote.service.SystemServerHook +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method + +object ZLUtils { + fun dumpArgs(frame: EmulatedStackFrame, skipFirst: Boolean = false): Array { + return mutableListOf().let { + val begin = if (skipFirst) 1 else 0 + for (index in begin ..< frame.type().parameterCount()) { + it.add(getArgument(frame, index)) + } + + it.toTypedArray() + } + } + + fun getArgument(frame: EmulatedStackFrame, index: Int): Any { + val accessor = frame.accessor() + + return when (accessor.getArgumentShorty(index)) { + 'L' -> accessor.getReference(index) + 'Z' -> accessor.getBoolean(index) + 'B' -> accessor.getByte(index) + 'C' -> accessor.getChar(index) + 'S' -> accessor.getShort(index) + 'I' -> accessor.getInt(index) + 'J' -> accessor.getLong(index) + 'F' -> accessor.getFloat(index) + 'D' -> accessor.getDouble(index) + else -> throw Exception("Should not reach here") + } + } + + fun setArgument(frame: EmulatedStackFrame, index: Int, value: Any) { + val accessor = frame.accessor() + + when (accessor.getArgumentShorty(index)) { + 'L' -> accessor.setReference(index, value) + 'Z' -> accessor.setBoolean(index, value as Boolean) + 'B' -> accessor.setByte(index, value as Byte) + 'C' -> accessor.setChar(index, value as Char) + 'S' -> accessor.setShort(index, value as Short) + 'I' -> accessor.setInt(index, value as Int) + 'J' -> accessor.setLong(index, value as Long) + 'F' -> accessor.setFloat(index, value as Float) + 'D' -> accessor.setDouble(index, value as Double) + else -> throw Exception("Should not reach here") + } + } + + fun setReturnValue(frame: EmulatedStackFrame, value: Any?) { + if (frame.type().returnType() != Void::class.java) { + frame.accessor().setValue(RETURN_VALUE_IDX, value) + } + } + + fun getStaticIntField(className: String, name: String) = getDeclaredField( + Class.forName(className), + name, + ).getInt(null) + + fun getIntField(obj: Any, name: String, clazz: Class<*>? = null) = getDeclaredField(clazz ?: obj.javaClass, name).getInt(obj) + + fun getBooleanField(obj: Any, name: String, clazz: Class<*>? = null) = getDeclaredField(clazz ?: obj.javaClass, name).getBoolean(obj) + + fun getObjectField(obj: Any, name: String, clazz: Class<*>? = null): Any? = getDeclaredField(clazz ?: obj.javaClass, name).get(obj) + + fun setBooleanField(obj: Any, name: String, value: Boolean) { + val field = getDeclaredField(obj.javaClass, name).apply { isAccessible = true } + field.setBoolean(obj, value) + } + + fun callMethodWithTypes(obj: Any, name: String, types: Array>, args: Array): Any? { + return getDeclaredMethod( + obj.javaClass, + name, + *types, + ).apply { isAccessible = true }.invoke(obj, *args) + } + + fun callMethod(obj: Any, name: String, vararg args: Any): Any? { + return getDeclaredMethod( + obj.javaClass, + name, + *args.map { it.javaClass }.toTypedArray() + ).apply { isAccessible = true }.invoke(obj, *args) + } + + fun callStaticMethod(clazz: Class<*>, name: String, vararg args: Any): Any? { + return getDeclaredMethod( + clazz, + name, + *args.map { it.javaClass }.toTypedArray() + ).apply { isAccessible = true }.invoke(null, *args) + } + + fun findConstructor(className: String, paramCount: Int = -1): Constructor<*>? { + val clazz = Class.forName(className, true, SystemServerHook.classLoader) + + return clazz.constructors.firstOrNull { + paramCount == -1 || it.parameterCount == paramCount + } + } + + fun findMethod(className: String, name: String, isDeclared: Boolean = false, systemClassLoader: Boolean = false, vararg args: Class<*>): Method { + val clazz = if (systemClassLoader) Class.forName(className, true, SystemServerHook.classLoader) else Class.forName(className) + return if (isDeclared) { + clazz.getDeclaredMethod(name, *args) + } else { + clazz.getMethod(name, *args) + } + } + + fun findField(clazz: Class<*>, name: String): Field? { + var currentClazz: Class<*> = clazz + var field: Field? = null + + while (field == null && currentClazz.javaClass.simpleName != "Object") { + field = runCatching { currentClazz.getField(name) }.getOrNull() + currentClazz = clazz.superclass.javaClass + } + + return field + } +} diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedConstants.kt b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZygoteConstants.kt similarity index 79% rename from xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedConstants.kt rename to zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZygoteConstants.kt index 26ef55165..78faacce1 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedConstants.kt +++ b/zygote/src/main/java/org/frknkrc44/hma_oss/zygote/util/ZygoteConstants.kt @@ -1,6 +1,6 @@ -package icu.nullptr.hidemyapplist.xposed +package org.frknkrc44.hma_oss.zygote.util -object XposedConstants { +object ZygoteConstants { const val COMPUTER_ENGINE_CLASS = "com.android.server.pm.ComputerEngine" const val PACKAGE_MANAGER_SERVICE_CLASS = "com.android.server.pm.PackageManagerService" const val PMS_COMPUTER_TRACKER_CLASS = $$"com.android.server.pm.PackageManagerService$ComputerTracker" @@ -10,9 +10,12 @@ object XposedConstants { const val ACCESSIBILITY_SERVICE_CLASS = "com.android.server.accessibility.AccessibilityManagerService" const val CONTENT_PROVIDER_TRANSPORT_CLASS = $$"android.content.ContentProvider$Transport" const val IMM_SERVICE_CLASS = "com.android.server.inputmethod.InputMethodManagerService" + const val IMM_IMPL_CLASS = "com.android.server.inputmethod.IInputMethodManagerImpl" const val PLATFORM_COMPAT_CLASS = "com.android.server.compat.PlatformCompat" const val ACTIVITY_STARTER_CLASS = "com.android.server.wm.ActivityStarter" const val ACTIVITY_TASK_SUPERVISOR_CLASS = "com.android.server.wm.ActivityTaskSupervisor" const val ACTIVITY_STACK_SUPERVISOR_CLASS = "com.android.server.wm.ActivityStackSupervisor" const val ZYGOTE_PROCESS_CLASS = "android.os.ZygoteProcess" + const val PROCESS_LIST_CLASS = "com.android.server.am.ProcessList" + const val PROCESS_RECORD_INTERNAL_CLASS = "com.android.server.am.psc.ProcessRecordInternal" }