diff --git a/README.md b/README.md index b5ca674..27d5171 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HMAL-OSS -Forked from [HMA-OSS]https://github.com/frknkrc44/HMA-OSS) +Forked from [HMA-OSS](https://github.com/frknkrc44/HMA-OSS) [![License](https://img.shields.io/github/license/frknkrc44/HMA-OSS?label=License)](https://choosealicense.com/licenses/gpl-3.0/) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 52daff5..b2d9a3f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -69,7 +69,7 @@ dependencies { android.applicationVariants.all { outputs.all { (this as BaseVariantOutputImpl).apply { - outputFileName = "${rootProject.name.replace(" ", "_")}-v${versionName}-${buildType.name}.apk" + outputFileName = "${rootProject.name.replace(" ", "_")}-${versionName}-${buildType.name}.apk" } } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 9ef9c9f..d5645a4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,8 +1,3 @@ -# Xposed --keepclassmembers class icu.nullptr.hidemyapplist.MyApp { - boolean isHooked; -} - # Enum class -keepclassmembers,allowoptimization enum * { public static **[] values(); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3a9950a..6ddd65a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,8 +6,6 @@ android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" /> - - ) { + + items.add(Category(getString(R.string.about_mychanges_title))) + items.add(Card(getString(R.string.about_mychanges_summary))) + items.add(Category(getString(R.string.title_about_fork))) items.add(Card(getString(R.string.about_fork_description))) @@ -46,10 +50,12 @@ class AboutActivity : AbsAboutActivity() { items.add(Line()) items.add(Contributor(R.drawable.cont_cpp_master, "LoveSy", "Idea provider", "https://github.com/yujincheng08")) + + items.add(Category(getString(R.string.about_fakedev))) + items.add(Contributor(R.drawable.cont_cuynu, "Cuynu", "another loser with no coding skills", "https://git.disroot.org/cuynu")) + items.add(Category(getString(R.string.about_support))) - items.add(Card("Github\nhttps://github.com/frknkrc44/HMA-OSS")) - items.add(Line()) - items.add(Card("Telegram\nhttps://t.me/aerathfuns")) + items.add(Card("Disroot\nhttps://git.disroot.org/cuynu/HMAL-OSS")) items.add(Category(getString(R.string.about_open_source))) items.add(License("MultiType", "drakeet", License.APACHE_2, "https://github.com/drakeet/MultiType")) diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppManageAdapter.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppManageAdapter.kt index 75f1713..e50c71d 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppManageAdapter.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppManageAdapter.kt @@ -1,16 +1,35 @@ package icu.nullptr.hidemyapplist.ui.adapter import android.view.ViewGroup +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import icu.nullptr.hidemyapplist.common.Constants.GMS_PACKAGE_NAME +import icu.nullptr.hidemyapplist.common.Constants.GSF_PACKAGE_NAME import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.ui.view.AppItemView +import org.frknkrc44.hma_oss.R class AppManageAdapter( private val onItemClickListener: (String) -> Unit ) : AppSelectAdapter() { + private val riskyPackages = arrayOf(GMS_PACKAGE_NAME, GSF_PACKAGE_NAME) inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) { init { view.setOnClickListener { + if (!PrefManager.bypassRiskyPackageWarning && riskyPackages.contains(view.binding.packageName.text)) { + MaterialAlertDialogBuilder(view.context) + .setTitle(R.string.app_warning_risky_package_title) + .setMessage(R.string.app_warning_risky_package_desc) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + onItemClickListener.invoke(filteredList[absoluteAdapterPosition]) + } + .show() + + return@setOnClickListener + } + onItemClickListener.invoke(filteredList[absoluteAdapterPosition]) } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppSelectAdapter.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppSelectAdapter.kt index 3474bc5..ba54f05 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppSelectAdapter.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/AppSelectAdapter.kt @@ -8,6 +8,7 @@ import icu.nullptr.hidemyapplist.ui.view.AppItemView import icu.nullptr.hidemyapplist.util.PackageHelper import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking +import org.frknkrc44.hma_oss.BuildConfig abstract class AppSelectAdapter( private val firstFilter: ((String) -> Boolean)? = null @@ -20,13 +21,15 @@ abstract class AppSelectAdapter( private inner class AppFilter : Filter() { override fun performFiltering(constraint: CharSequence): FilterResults { return runBlocking { - val constraintLowered = constraint.toString().lowercase() + val constraintLowered = constraint.toString().trim().lowercase() val filteredList = PackageHelper.appList.first().filter { if (firstFilter?.invoke(it) == false) return@filter false if (!PrefManager.appFilter_showSystem && PackageHelper.isSystem(it)) return@filter false + if (it == BuildConfig.APPLICATION_ID && + (this@AppSelectAdapter.javaClass == AppManageAdapter::class.java || + (this@AppSelectAdapter.javaClass == AppScopeAdapter::class.java && firstFilter != null))) return@filter false val label = PackageHelper.loadAppLabel(it) - val packageInfo = PackageHelper.loadPackageInfo(it) - label.lowercase().contains(constraintLowered) || packageInfo.packageName.lowercase().contains(constraintLowered) + label.lowercase().contains(constraintLowered) || it.lowercase().contains(constraintLowered) } FilterResults().also { it.values = filteredList } @@ -35,8 +38,11 @@ abstract class AppSelectAdapter( @Suppress("UNCHECKED_CAST", "NotifyDataSetChanged") override fun publishResults(constraint: CharSequence, results: FilterResults) { - filteredList = results.values as List - notifyDataSetChanged() + val values = results.values + if (values != null) { + filteredList = values as List + notifyDataSetChanged() + } } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSelectFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSelectFragment.kt index b446adb..066b962 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSelectFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSelectFragment.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.MenuItem import android.view.View import android.view.WindowInsets +import android.widget.TextView import androidx.activity.addCallback import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter @@ -20,10 +22,11 @@ import icu.nullptr.hidemyapplist.util.PackageHelper import kotlinx.coroutines.launch import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentAppSelectBinding +import org.frknkrc44.hma_oss.ui.fragment.AppPresetFragment abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { - private val binding by viewBinding() + protected val binding by viewBinding() protected abstract val firstComparator: Comparator protected abstract val adapter: AppSelectAdapter @@ -34,17 +37,21 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { navController.navigateUp() } - private fun applyFilter() { + protected fun applyFilter() { adapter.filter.filter(search) } - private fun sortList() { + protected open fun sortList() { lifecycleScope.launch { PackageHelper.sortList(firstComparator) applyFilter() } } + open fun invalidateCache() { + PackageHelper.invalidateCache() + } + private fun onMenuOptionSelected(item: MenuItem) { when (item.itemId) { R.id.menu_show_system -> { @@ -75,11 +82,13 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { sortList() } + open fun getFragmentTitle() = getString(R.string.title_app_select) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() } setupToolbar( toolbar = binding.toolbar, - title = getString(R.string.title_app_select), + title = getFragmentTitle(), navigationIcon = R.drawable.baseline_arrow_back_24, navigationOnClick = { onBack() }, menuRes = R.menu.menu_app_list, @@ -110,9 +119,16 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { binding.list.layoutManager = LinearLayoutManager(context) binding.list.adapter = adapter binding.swipeRefresh.setOnRefreshListener { - PackageHelper.invalidateCache() + invalidateCache() } + adapter.registerAdapterDataObserver( + EmptyDataObserver( + binding.list, + binding.listEmptyContainer + ) + ) + lifecycleScope.launch { PackageHelper.isRefreshing .flowWithLifecycle(lifecycle) @@ -123,23 +139,66 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { sortList() - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets + } + } + + // Credit: https://medium.com/nerd-for-tech/empty-dataset-in-recyclerview-ad86833dd5c6 + inner class EmptyDataObserver( + private val recyclerView: RecyclerView, + private val emptyView: View + ): RecyclerView.AdapterDataObserver() { + private val fragmentType by lazy { + when(this@AppSelectFragment.javaClass) { + ScopeFragment::class.java -> 0 + AppPresetFragment::class.java -> 1 + else -> 2 + } + } + + val atLeastEntryCount: Int get() { + if (!search.isBlank()) return 0 + + return if (fragmentType < 2) 1 else 2 + } + + private fun checkIfEmpty() { + val emptyViewVisible = recyclerView.adapter!!.itemCount < atLeastEntryCount + emptyView.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE + if (emptyViewVisible) { + emptyView.findViewById(R.id.list_empty_text).text = getString( + when (fragmentType) { + 0 -> R.string.list_empty_no_enabled + 1 -> R.string.list_empty_preset + else -> R.string.list_empty_no_apps + } + ) + } + recyclerView.visibility = if (emptyViewVisible) View.GONE else View.VISIBLE + } + + override fun onChanged() { + super.onChanged() + checkIfEmpty() } } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSettingsFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSettingsFragment.kt index 2fba4de..7b73f27 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSettingsFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/AppSettingsFragment.kt @@ -5,6 +5,7 @@ import android.os.Build import android.os.Bundle import android.view.View import android.view.WindowInsets +import android.widget.Toast import androidx.activity.addCallback import androidx.core.graphics.drawable.toDrawable import androidx.fragment.app.Fragment @@ -76,23 +77,26 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { .commit() } - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets } } @@ -103,6 +107,9 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { "enableHide" -> pack.enabled "useWhiteList" -> pack.config.useWhitelist "excludeSystemApps" -> pack.config.excludeSystemApps + "hideInstallationSource" -> pack.config.hideInstallationSource + "hideSystemInstallationSource" -> pack.config.hideSystemInstallationSource + "excludeTargetInstallationSource" -> pack.config.excludeTargetInstallationSource else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -112,6 +119,9 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { "enableHide" -> pack.enabled = value "useWhiteList" -> pack.config.useWhitelist = value "excludeSystemApps" -> pack.config.excludeSystemApps = value + "hideInstallationSource" -> pack.config.hideInstallationSource = value + "hideSystemInstallationSource" -> pack.config.hideSystemInstallationSource = value + "excludeTargetInstallationSource" -> pack.config.excludeTargetInstallationSource = value else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -152,6 +162,21 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { it.title = PackageHelper.loadAppLabel(pack.app) it.summary = PackageHelper.loadPackageInfo(pack.app).packageName } + findPreference("hideInstallationSource")?.setOnPreferenceChangeListener { _, newValue -> + Toast.makeText(requireContext(), + R.string.app_force_stop_warning, Toast.LENGTH_LONG).show() + true + } + findPreference("hideSystemInstallationSource")?.setOnPreferenceChangeListener { _, newValue -> + Toast.makeText(requireContext(), + R.string.app_force_stop_warning, Toast.LENGTH_LONG).show() + true + } + findPreference("excludeTargetInstallationSource")?.setOnPreferenceChangeListener { _, newValue -> + Toast.makeText(requireContext(), + R.string.app_force_stop_warning, Toast.LENGTH_LONG).show() + true + } findPreference("useWhiteList")?.setOnPreferenceChangeListener { _, newValue -> pack.config.applyTemplates.clear() pack.config.extraAppList.clear() 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 04e3d97..b55a1ca 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 @@ -143,23 +143,26 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { binding.list.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) updateLogs() - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets } } } 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 79d504a..05b8ecf 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 @@ -57,23 +57,26 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen .commit() } - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets } } @@ -94,6 +97,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen "blackDarkTheme" -> PrefManager.blackDarkTheme "detailLog" -> ConfigManager.detailLog "hideIcon" -> PrefManager.hideIcon + "bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning "appDataIsolation" -> CommonUtils.isAppDataIsolationEnabled "voldAppDataIsolation" -> CommonUtils.isVoldAppDataIsolationEnabled "forceMountData" -> ConfigManager.forceMountData @@ -118,6 +122,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen "detailLog" -> ConfigManager.detailLog = value "forceMountData" -> ConfigManager.forceMountData = value "hideIcon" -> PrefManager.hideIcon = value + "bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning = value "appDataIsolation" -> Unit "voldAppDataIsolation" -> Unit else -> throw IllegalArgumentException("Invalid key: $key") @@ -264,6 +269,11 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen configureDataIsolation() + findPreference("bypassRiskyPackageWarning")?.setOnPreferenceChangeListener { _, newValue -> + activity?.recreate() + true + } + findPreference("stopSystemService")?.setOnPreferenceClickListener { if (ServiceClient.serviceVersion != 0) { MaterialAlertDialogBuilder(requireContext()) diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateManageFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateManageFragment.kt index d6bab23..4bb088b 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateManageFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateManageFragment.kt @@ -1,6 +1,5 @@ package icu.nullptr.hidemyapplist.ui.fragment -import android.graphics.Color import android.os.Build import android.os.Bundle import android.view.View @@ -12,7 +11,6 @@ import androidx.fragment.app.setFragmentResultListener import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.recyclerview.widget.LinearLayoutManager import by.kirich1409.viewbindingdelegate.viewBinding -import com.google.android.material.transition.MaterialContainerTransform import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.ui.adapter.TemplateAdapter @@ -26,14 +24,6 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { private val binding by viewBinding() private val adapter = TemplateAdapter(this::navigateToSettings) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - sharedElementEnterTransition = MaterialContainerTransform().apply { - drawingViewId = R.id.nav_host_fragment - scrimColor = Color.TRANSPARENT - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setupToolbar( toolbar = binding.toolbar, @@ -53,23 +43,26 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { binding.templateList.layoutManager = LinearLayoutManager(context) binding.templateList.adapter = adapter - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateSettingsFragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateSettingsFragment.kt index 1ae2eec..8898ab6 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateSettingsFragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/fragment/TemplateSettingsFragment.kt @@ -56,16 +56,6 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) { navController.navigateUp() } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - /* - sharedElementEnterTransition = MaterialContainerTransform().apply { - drawingViewId = R.id.nav_host_fragment - scrimColor = Color.TRANSPARENT - } - */ - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack(false) } setupToolbar( @@ -80,6 +70,7 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) { ) binding.templateName.setText(viewModel.name) + binding.workMode.setText(if (viewModel.isWhiteList) R.string.whitelist else R.string.blacklist) binding.templateName.addTextChangedListener { viewModel.name = it.toString() } binding.targetApps.setOnClickListener { setFragmentResultListener("app_select") { _, bundle -> @@ -119,23 +110,26 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) { } } - val insets = binding.root.rootWindowInsets - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( - barInsets.left, - barInsets.top, - barInsets.right, - barInsets.bottom, - ) - } else { - @Suppress("deprecation") - binding.root.setPadding( - insets.systemWindowInsetLeft, - insets.systemWindowInsetTop, - insets.systemWindowInsetRight, - insets.systemWindowInsetBottom, - ) + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets } } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/AppItemView.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/AppItemView.kt index 48e2af7..1589e39 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/AppItemView.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/AppItemView.kt @@ -15,7 +15,7 @@ class AppItemView @JvmOverloads constructor( defStyleRes: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { - private val binding by viewBinding(createMethod = CreateMethod.INFLATE) + val binding by viewBinding(createMethod = CreateMethod.INFLATE) var showEnabled: Boolean get() = binding.enabled.visibility == VISIBLE @@ -35,7 +35,12 @@ class AppItemView @JvmOverloads constructor( fun load(packageName: String) { binding.packageName.text = packageName - binding.label.text = PackageHelper.loadAppLabel(packageName) - binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName)) + try { + binding.label.text = PackageHelper.loadAppLabel(packageName) + binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName)) + } catch (e: Throwable) { + binding.label.text = packageName + binding.icon.setImageResource(android.R.drawable.sym_def_app_icon) + } } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt b/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt index d360147..950fbb2 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt @@ -26,11 +26,15 @@ object PackageHelper { val icon: Bitmap ) - private object Comparators { + object Comparators { val byLabel = Comparator { o1, o2 -> - val n1 = loadAppLabel(o1).lowercase(Locale.getDefault()) - val n2 = loadAppLabel(o2).lowercase(Locale.getDefault()) - Collator.getInstance(Locale.getDefault()).compare(n1, n2) + try { + val n1 = loadAppLabel(o1).lowercase(Locale.getDefault()) + val n2 = loadAppLabel(o2).lowercase(Locale.getDefault()) + Collator.getInstance(Locale.getDefault()).compare(n1, n2) + } catch (_: Throwable) { + byPackageName.compare(o1, o2) + } } val byPackageName = Comparator { o1, o2 -> val n1 = o1.lowercase(Locale.getDefault()) @@ -38,14 +42,22 @@ object PackageHelper { Collator.getInstance(Locale.getDefault()).compare(n1, n2) } val byInstallTime = Comparator { o1, o2 -> - val n1 = loadPackageInfo(o1).firstInstallTime - val n2 = loadPackageInfo(o2).firstInstallTime - n2.compareTo(n1) + try { + val n1 = loadPackageInfo(o1).firstInstallTime + val n2 = loadPackageInfo(o2).firstInstallTime + n2.compareTo(n1) + } catch (_: Throwable) { + byPackageName.compare(o1, o2) + } } val byUpdateTime = Comparator { o1, o2 -> - val n1 = loadPackageInfo(o1).lastUpdateTime - val n2 = loadPackageInfo(o2).lastUpdateTime - n2.compareTo(n1) + try { + val n1 = loadPackageInfo(o1).lastUpdateTime + val n2 = loadPackageInfo(o2).lastUpdateTime + n2.compareTo(n1) + } catch (_: Throwable) { + byPackageName.compare(o1, o2) + } } } @@ -99,6 +111,10 @@ object PackageHelper { mAppList.emit(list) } + fun exists(packageName: String) = runBlocking { + packageCache.first().contains(packageName) + } + fun loadPackageInfo(packageName: String): PackageInfo = runBlocking { packageCache.first()[packageName]!!.info } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetAdapter.kt new file mode 100644 index 0000000..6185b3b --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetAdapter.kt @@ -0,0 +1,74 @@ +package org.frknkrc44.hma_oss.ui.adapter + +import android.view.ViewGroup +import android.widget.Filter +import icu.nullptr.hidemyapplist.service.ServiceClient +import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter +import icu.nullptr.hidemyapplist.ui.view.AppItemView +import icu.nullptr.hidemyapplist.util.PackageHelper +import kotlinx.coroutines.runBlocking + +class AppPresetAdapter( + private val presetName: String +) : AppSelectAdapter() { + var packages = mutableListOf() + + fun updateList() { + packages.clear() + packages += ServiceClient.getPackagesForPreset(presetName)?.toList() ?: listOf() + } + + inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) { + override fun bind(packageName: String) { + (itemView as AppItemView).let { + it.load(packageName) + it.alpha = if (!PackageHelper.exists(packageName)) 0.5f else 1.0f + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = AppItemView(parent.context, false) + view.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + return ViewHolder(view) + } + + private inner class PresetFilter : Filter() { + + override fun performFiltering(constraint: CharSequence): FilterResults { + return runBlocking { + val constraintLowered = constraint.toString().trim().lowercase() + val filteredList = packages.filter { + if (constraintLowered.isEmpty()) return@filter true + + if (it.lowercase().contains(constraintLowered)) return@filter true + + try { + val label = PackageHelper.loadAppLabel(it) + return@filter label.lowercase().contains(constraintLowered) + } catch (e: Throwable) { + return@filter false + } + } + + FilterResults().also { it.values = filteredList } + } + } + + @Suppress("UNCHECKED_CAST", "NotifyDataSetChanged") + override fun publishResults(constraint: CharSequence, results: FilterResults) { + val values = results.values + if (values != null) { + filteredList = values as List + notifyDataSetChanged() + } + } + } + + private val mFilter = PresetFilter() + + override fun getFilter(): Filter = mFilter +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetListAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetListAdapter.kt new file mode 100644 index 0000000..d5445b1 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/AppPresetListAdapter.kt @@ -0,0 +1,76 @@ +package org.frknkrc44.hma_oss.ui.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import icu.nullptr.hidemyapplist.common.AppPresets +import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.ui.view.ListItemView +import org.frknkrc44.hma_oss.BuildConfig +import org.frknkrc44.hma_oss.R + +class AppPresetListAdapter( + context: Context, + private val onClickListener: ((ConfigManager.PresetInfo) -> Unit)? +) : RecyclerView.Adapter() { + + private lateinit var list: List + + init { + updateList(context) + } + + inner class ViewHolder(view: ListItemView) : RecyclerView.ViewHolder(view) { + init { + view.setOnClickListener { + onClickListener?.invoke(list[absoluteAdapterPosition]) + } + } + + fun bind(presetName: String) { + with(itemView as ListItemView) { + setIcon(R.drawable.baseline_assignment_24) + text = presetName + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = ListItemView(parent.context) + view.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + return ViewHolder(view) + } + + override fun getItemCount() = list.size + + override fun getItemId(position: Int) = list[position].name.hashCode().toLong() + + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(list[position].translation) + + @SuppressLint("NotifyDataSetChanged", "DiscouragedApi") + private fun updateList(context: Context) { + val presetNames = AppPresets.instance.getAllPresetNames() + val presetTranslations = presetNames.map { name -> + try { + val id = context.resources.getIdentifier( + "preset_${name}", + "string", + BuildConfig.APPLICATION_ID + ) + + return@map if (id != 0) { context.resources.getString(id) } else { name } + } catch (_: Throwable) {} + + name + } + + list = presetNames + .map { ConfigManager.PresetInfo(it, presetTranslations[presetNames.indexOf(it)]) } + .sortedWith { a, b -> a.translation.lowercase().compareTo(b.translation.lowercase()) } + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AppPresetFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AppPresetFragment.kt new file mode 100644 index 0000000..3ada7a2 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AppPresetFragment.kt @@ -0,0 +1,70 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.navArgs +import icu.nullptr.hidemyapplist.hmaApp +import icu.nullptr.hidemyapplist.service.PrefManager +import icu.nullptr.hidemyapplist.ui.fragment.AppSelectFragment +import icu.nullptr.hidemyapplist.util.PackageHelper +import icu.nullptr.hidemyapplist.util.PackageHelper.Comparators +import kotlinx.coroutines.launch +import org.frknkrc44.hma_oss.ui.adapter.AppPresetAdapter + +class AppPresetFragment() : AppSelectFragment() { + + override val firstComparator: Comparator = Comparator.comparing(PackageHelper::exists).reversed() + + override val adapter by lazy { + val args by navArgs() + AppPresetAdapter(args.presetName) + } + + override fun getFragmentTitle(): String { + val args by navArgs() + return args.presetTitle + } + + override fun sortList() { + hmaApp.globalScope.launch { + sortPresetList() + + lifecycleScope.launch { + applyFilter() + } + } + } + + fun sortPresetList() { + var comparator = when (PrefManager.appFilter_sortMethod) { + PrefManager.SortMethod.BY_LABEL -> Comparators.byLabel + PrefManager.SortMethod.BY_PACKAGE_NAME -> Comparators.byPackageName + PrefManager.SortMethod.BY_INSTALL_TIME -> Comparators.byInstallTime + PrefManager.SortMethod.BY_UPDATE_TIME -> Comparators.byUpdateTime + } + if (PrefManager.appFilter_reverseOrder) comparator = comparator.reversed() + + val packages = adapter.packages.sortedWith(firstComparator.then(comparator)) + + lifecycleScope.launch { + adapter.packages.clear() + adapter.packages += packages + } + } + + override fun invalidateCache() { + super.invalidateCache() + adapter.updateList() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + lifecycleScope.launch { + binding.swipeRefresh.isRefreshing = true + adapter.updateList() + sortList() + } + } +} \ No newline at end of file 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 6327c59..68b0a8c 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 @@ -116,7 +116,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) { val serviceVersion = ServiceClient.serviceVersion val color = when { - !hmaApp.isHooked -> getColor(R.color.gray) serviceVersion == 0 -> getColor(R.color.invalid) else -> themeColor(android.R.attr.colorPrimary) } @@ -126,7 +125,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) { root.outlineAmbientShadowColor = color root.outlineSpotShadowColor = color - if (hmaApp.isHooked) { + if (serviceVersion > 0) { moduleStatusIcon.setImageResource(R.drawable.sentiment_calm_24px) val versionNameSimple = BuildConfig.VERSION_NAME.substringBefore(".r") moduleStatus.text = @@ -268,6 +267,14 @@ class HomeFragment : Fragment(R.layout.fragment_home) { } } + with(binding.managePresets) { + text1.text = getString(R.string.title_preset_manage) + icon.setImageResource(R.drawable.baseline_my_location_24) + root.setOnClickListener { + navigate(R.id.nav_presets) + } + } + with(binding.navLogs) { text1.text = getString(R.string.title_logs) icon.setImageResource(R.drawable.outline_assignment_24) diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/PresetManageFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/PresetManageFragment.kt new file mode 100644 index 0000000..6849c61 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/PresetManageFragment.kt @@ -0,0 +1,65 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.WindowInsets +import androidx.core.view.doOnPreDraw +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import by.kirich1409.viewbindingdelegate.viewBinding +import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.ui.util.navController +import icu.nullptr.hidemyapplist.ui.util.setupToolbar +import org.frknkrc44.hma_oss.R +import org.frknkrc44.hma_oss.databinding.FragmentPresetManageBinding +import org.frknkrc44.hma_oss.ui.adapter.AppPresetListAdapter + +class PresetManageFragment : Fragment(R.layout.fragment_preset_manage) { + + private val binding by viewBinding() + private val adapter by lazy { + AppPresetListAdapter(requireContext(), this::navigateToPreset) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setupToolbar( + toolbar = binding.toolbar, + title = getString(R.string.title_preset_manage), + navigationIcon = R.drawable.baseline_arrow_back_24, + navigationOnClick = { navController.navigateUp() } + ) + postponeEnterTransition() + view.doOnPreDraw { startPostponedEnterTransition() } + + binding.presetList.layoutManager = LinearLayoutManager(context) + binding.presetList.adapter = adapter + + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + binding.root.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + binding.root.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets + } + } + + private fun navigateToPreset(presetInfo: ConfigManager.PresetInfo) { + val args = AppPresetFragmentArgs(presetInfo.name, presetInfo.translation) + navController.navigate(R.id.nav_preset_inner_manage, args.toBundle()) + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/cont_cuynu.webp b/app/src/main/res/drawable/cont_cuynu.webp new file mode 100644 index 0000000..749d2e9 Binary files /dev/null and b/app/src/main/res/drawable/cont_cuynu.webp differ diff --git a/app/src/main/res/drawable/outline_shop_24.xml b/app/src/main/res/drawable/outline_shop_24.xml new file mode 100644 index 0000000..41bce8c --- /dev/null +++ b/app/src/main/res/drawable/outline_shop_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_app_select.xml b/app/src/main/res/layout/fragment_app_select.xml index 75b2aa2..696c165 100644 --- a/app/src/main/res/layout/fragment_app_select.xml +++ b/app/src/main/res/layout/fragment_app_select.xml @@ -32,10 +32,42 @@ android:clipToPadding="false" android:scrollbars="vertical"> - + android:layout_height="match_parent"> + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 798f570..5f03305 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -54,6 +54,10 @@ android:id="@+id/manage_templates" layout="@layout/view_home_item" /> + + @@ -75,6 +79,8 @@ android:id="@+id/backup_config" app:icon="@drawable/outline_backup_24" android:text="@string/home_backup_config" + android:maxLines="1" + android:ellipsize="end" android:layout_marginRight="8dp" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -84,6 +90,8 @@ android:id="@+id/restore_config" app:icon="@drawable/outline_settings_backup_restore_24" android:text="@string/home_restore_config" + android:maxLines="1" + android:ellipsize="end" android:layout_marginLeft="8dp" android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/layout/fragment_preset_manage.xml b/app/src/main/res/layout/fragment_preset_manage.xml new file mode 100644 index 0000000..2544f26 --- /dev/null +++ b/app/src/main/res/layout/fragment_preset_manage.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_template_settings.xml b/app/src/main/res/layout/fragment_template_settings.xml index 8be7422..729eed5 100644 --- a/app/src/main/res/layout/fragment_template_settings.xml +++ b/app/src/main/res/layout/fragment_template_settings.xml @@ -39,6 +39,28 @@ android:layout_height="wrap_content" /> + + + + + + + + + + + + + + Beranda + Log + Pengaturan + Kelola templat + Pengaturan templat + Kelola aplikasi + Pilih aplikasi + Pengaturan aplikasi + Tentang + Tentang fork ini + Jangan instal HMA pada pengguna sekunder + Tampilkan log kerusakan + File konfigurasi rusak. Harap bersihkan data aplikasi + Edit daftar + Menghapus + Diaktifkan + Dinonaktifkan + Mode kerja + Blacklist + Whitelist + Aplikasi + Preferensi + Segarkan + Filter + Ya + Tidak + Cari + Sistem + Ikut sistem + + Modul Diaktifkan [%s] + Modul Tidak Diaktifkan + Layanan sistem sedang berjalan [%d] + Layanan sistem tidak berjalan + Modul diperbarui, perlu mulai ulang + HMA-OSS telah memfilter total %d permintaan + Kelola + Uji deteksi + Unduh aplikasi uji + Kami telah mengembangkan aplikasi uji deteksi individual dan memigrasikan uji deteksi ke dalamnya. Ingin mengunduhnya sekarang? + Cadangkan & Pulihkan + Konfigurasi cadangan + Konfigurasi pemulihan + Impor berhasil + Impor gagal + Versi modul lebih rendah dari versi cadangan + Versi cadangan terlalu lama + File cadangan rusak + Ekspor gagal + Konfigurasi yang dicadangkan + Pembaruan baru tersedia: %s + Memperbarui: %s + + Tampilkan aplikasi sistem + Urutkan + Urutkan terbalik + Nama aplikasi + Nama paket + Waktu instalasi + Waktu pembaruan + + Buat templat untuk aplikasi \"restless\", guna mengontrol akses daftar aplikasi.\n\nBlacklist (disarankan): aplikasi yang dipilih dalam templat akan tersembunyi dari aplikasi yang menerapkan templat tersebut.\nWhitelist: aplikasi yang tidak dipilih dalam templat akan tersembunyi oleh aplikasi yang menerapkan templat tersebut. + Buat templat blacklist + Buat templat whitelist + Apakah anda yakin ingin menghapus templat tersebut? + Nama templat + Nama tidak valid + Hapus Templat + Nama templat sudah ada + Diterapkan ke %d aplikasi + %d apl terlihat + %d apl tidak terlihat + + Aktifkan penyembunyian + Konfigurasi templat + Pilih templat + Pilih preset + Kecualikan aplikasi sistem + Matikan opsi ini hanya jika benar-benar diperlukan. Kemungkinan menyebabkan crash jika dinonaktifkan + Menggunakan %d templat + Menggunakan %d preset + Menggunakan %d preset pengaturan + %d apl tambahan terlihat + %d apl tambahan tidak terlihat + Paket berisiko + Paket ini ditandai sebagai berisiko dan dapat menyebabkan masalah pada aplikasi tersembunyi. Apakah anda ingin melanjutkan? + Spoofing + Sumber instalasi spoof + Semua aplikasi pengguna akan di-spoof seolah-olah terpasang di Play Store untuk aplikasi ini + Spoof apl sistem juga + Semua aplikasi sistem akan di-spoof seolah-olah terpasang tanpa alasan apa pun untuk aplikasi ini + Kecualikan aplikasi target + Anda dapat mengaktifkannya jika aplikasi ini tidak tersedia di Play Store + Jangan lupa untuk menghentikan paksa aplikasi target setelah menyimpan konfigurasi + + Modul LSPosed/Xposed + Manajer root/Aplikasi root + Aplikasi ROM kustom + + Aksesibilitas + Opsi pengembang + + Simpan log + Hapus log + Tidak ada log tersimpan + Log disimpan + + Modul + Isolasi data + Data apl: %s\nData apl Vold: %s\nPaksa mount: %s + Opsi ini hanya tersedia di Android 11 dan di atasnya + Isolasi data aplikasi + Isolasi data aplikasi Vold + Peringatan + Ini akan mengaktifkan fitur AOSP yang membuat direktori penyimpanan pribadi eksternal aplikasi terisolasi. Ini dapat membantu meningkatkan penyembunyian aplikasi meskipun dapat menyebabkan masalah stabilitas. Fitur ini dapat berhenti berfungsi kapan saja karena ditinggalkan oleh AOSP. + Perubahan diperlukan izin root + Perubahan diperlukan reboot + Izin ditolak + Paksa isolasi data untuk semua aplikasi + Isolasi data dinonaktifkan untuk aplikasi target API yang lebih rendah dari 30 secara default + Log detail untuk debugging + Ukuran buffer logger + Sembunyikan ikon modul di launcher + Anda masih dapat membuka aplikasi di manajer Xposed + Lewati peringatan paket berisiko + Perlu diketahui bahwa pengembang tidak bertanggung jawab atas masalah terkait paket berisiko sebelum atau setelah anda mengaktifkan fitur ini + Bahasa + Berpartisipasi dalam penerjemahan + Bantu kami menerjemahkan %s ke bahasa anda + Tema + Warna tema sistem + Warna tema + Tema gelap + Tema gelap hitam murni + Gunakan tema hitam murni jika tema gelap diaktifkan + Layanan + Hentikan layanan sistem + Perlu reboot untuk memulai kembali layanan + Bersihkan lingkungan runtime pada saat yang bersamaan? + Hapus cache layanan sistem di /data/misc/hide_my_applist_*. Setelah reboot berikutnya, Diperlukan membuka aplikasi modul sekali secara manual untuk mengaktifkan intersepsi, dan jumlah intersepsi juga akan dihapus. + Paksa bersihkan lingkungan runtime (root) + Hanya jika layanan sistem gagal untuk memulai, jika tidak, Anda harus mengklik opsi di atas. + Lingkungan runtime dibersihkan + Pembaruan + Nonaktifkan pemberitahuan pembaruan + Terima pembaruan beta + Versi beta mungkin tidak stabil + + Meskipun tidak tepat untuk mendeteksi instalasi aplikasi tertentu, tidak semua aplikasi yang menggunakan root mendukung nama paket acak. Dalam kasus ini, aplikasi terdeteksi yang menggunakan root (seperti Lokasi Palsu dan Isolasi Penyimpanan) dianggap sama dengan root itu sendiri.\n\nPada saat yang sama, beberapa aplikasi cerdas memanfaatkan celah untuk mendapatkan daftar aplikasi anda, sehingga mereka bisa membangun profil tentang anda.\n\nModul ini menyediakan beberapa metode untuk menguji apakah anda telah menyembunyikan daftar aplikasi dengan baik. Selain itu, dapat berfungsi sebagai modul Xposed untuk menyembunyikan aplikasi tertentu atau menolak permintaan daftar aplikasi guna melindungi privasi anda. + + Proyek HMA asli telah menjadi sumber tertutup dan kode terbaru mereka tidak memuat beberapa perubahan terbaru, jadi saya membuat percabangan ini untuk melanjutkannya sebagai sumber terbuka lagi. + + Cara menggunakan modul ini + Anda dapat membuat template di \"Kelola templat\".\nKemudian terapkan templat ke aplikasi di \"Kelola aplikasi\". (Anda juga dapat memilih aplikasi tambahan untuk target.)\nAnda harus dan HANYA memeriksa \"Kerangka Sistem\" dalam scope modul Xposed / whitelist. + + Modifikasi konfigurasi efektif secara real time. + + Pengembang + Dukungan dan umpan balik + Lisensi sumber terbuka + + Sakura + Merah + Merah muda + Ungu + Ungu tua + Biru nila + Biru + Biru muda + Sian + Toska + Hijau + Hijau muda + Hijau limau + Kuning + Kuning amber + Oranye + Oranye tua + Cokelat + Biru abu + + Tema gelap + Selalu mati + Selalu aktif + diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 4b5b111..2df9dc2 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -1,5 +1,7 @@ + HMA-OSS + ホーム ログ 設定 @@ -8,14 +10,16 @@ アプリの管理 アプリの選択 アプリの設定 - About - Do not install HMAL to secondary user + このアプリについて + このフォークについて + + HMA をセカンダリユーザーにインストールしないでください クラッシュログの表示 - 構成ファイルが破損しています。 アプリのデータを消去してください。 + 設定ファイルが破損しています。アプリデータを削除してください リストの編集 削除 有効 - Disabled + 無効 作業モード ブラックリスト ホワイトリスト @@ -25,110 +29,158 @@ フィルター はい いいえ - Search - System + 検索 + システム + システムに従う + モジュールは有効です [%s] モジュールは無効です システムサービスの実行中 [%d] システムサービスは実行されていません - モジュールが更新されました。再起動してください。 - HMAL-OSS は合計 %d リクエストをフィルタリングしました + モジュールが更新されました。再起動してください + HMA-OSS は合計 %d 件のリクエストをフィルタリングしました 管理 検出のテスト テストアプリのダウンロード - 個別検知テストアプリを開発し、検知テストを移行しました。 今すぐダウンロードしますか? - バックアップ & リストア - コンフィグのバックアップ - コンフィグのリストア + 個別検知テストアプリを開発し、検知テストを移行しました。今すぐダウンロードしますか? + バックアップ & 復元 + 設定をバックアップ + 設定を復元 インポート成功 - インポートの失敗 - バックアップよりも低いモジュール バージョン + インポート失敗 + モジュールのバージョンが古すぎます バックアップのバージョンが古すぎます バックアップは破損しています 書き出しの失敗 - バックアップ済みのコンフィグ + 設定をバックアップしました 利用可能なアップデート: %s アップデート: %s + - Show system apps + システムアプリを表示 並び替え 逆順 アプリ名で パッケージネームで インストール日時で アップデート時間で + 「落ち着きのない」アプリのテンプレートを確立し、取得できるアプリリストを制御します。\n\nブラックリスト (推奨): テンプレートで選択されたアプリは、テンプレートを適用するアプリから見えなくなります。\nホワイトリスト: テンプレートで選択されていないアプリは、テンプレートを適用するアプリには表示されません。 ブラックリストのテンプレートを作成 - ホワイトリストのテンプレートの作成 + ホワイトリストのテンプレートを作成 テンプレートを削除してもよろしいですか? テンプレート名 無効な名前 - Delete Template + テンプレートを削除 このテンプレート名は既に存在します %d 個のアプリに適用 %d 個のアプリが表示されます %d 個のアプリが表示されません + 非表示を有効にする - コンフィグのテンプレート - テンプレートのチョイス + テンプレート設定 + テンプレートを選択する + プリセットを選択する システムアプリを除外 - 本当に必要な場合にのみ、このオプションをオフにしてください。 無効にするとクラッシュする可能性があります - %d テンプレートの使用 + 本当に必要な場合にのみ、このオプションをオフにしてください。無効にするとクラッシュする可能性があります + %d 個のテンプレートを使用中 + %d 個のプリセットを使用中 + %d 個のプリセット設定を使用中 %d 個の追加アプリが表示されます %d 個の追加アプリが表示されません + 危険なパッケージ + このパッケージは危険とマークされており、非表示のアプリに問題を引き起こす可能性があります。続行しますか? + 偽装 + インストールソースを偽装する + このアプリケーションでは、すべてのユーザーアプリが Play ストア経由でインストールされたように偽装されます + システムアプリにも偽装する + このアプリケーションでは、すべてのシステムアプリが何もインストールされていないように偽装されます + 対象アプリを除外する + このアプリが Play ストアで利用できない場合、有効にすることができます + 設定を保存した後に対象のアプリを強制終了することを忘れないでください + + + LSPosed/Xposed モジュール + Root マネージャー/Rooted アプリ + カスタム ROM アプリ + + + アクセシビリティ + 開発者向けオプション + ログの保存 ログのクリア 保存するログがありません ログを保存しました + モジュール - Data isolation - App data: %s\nVold app data: %s\nForce mount: %s - This option is only available on Android 11 and above - App data isolation - Vold app data isolation - Changing requires root permission - Changing requires reboot - Permission denied - Enforce data isolation for all apps - Data isolation is disabled for apps target API lower than 30 by default + データ分離 + アプリデータ: %s\nVold アプリデータ: %s\n強制マウント: %s + このオプションは Android 11 以上で使用できます + アプリのデータ分離 + Vold アプリのデータ分離 + 警告 + この機能は AOSP の「アプリの外部ストレージディレクトリを隔離する」という機能を有効化します。これにより、アプリの非表示機能が向上する可能性がありますが、システムの安定性に問題が生じる可能性もあります。また、この機能は AOSP で廃止されているため、いつ動作しなくなるか分からない点にも注意が必要です。 + 変更には root 権限が必要です + 変更には再起動が必要です + 権限が拒否されました + 全てのアプリに対してデータ分離を強制する + ターゲット API が 30 未満のアプリに対してはデフォルトでデータ分離が無効になっています デバッグ用詳細ログ ロガーのバッファサイズ - ランチャーでモジュール アイコンを非表示にする - Xposedマネージャーで引き続きアプリを開くことができます + ランチャーでモジュールアイコンを非表示にする + LSPosed マネージャーで引き続きアプリを開くことができます + 危険なパッケージに関する警告を無視する + この機能を有効にする前後で、危険なパッケージに関する問題が発生した場合、開発者は一切責任を負わないことをご了承ください + 言語 翻訳に参加する %s をあなたの言語に翻訳するのにご協力ください + テーマ システムのテーマカラー テーマカラー ダークテーマ ピュアブラックテーマ - ブラック有効になっている場合は、ピュアブラックのテーマを使用してください + ダークテーマが有効になっている場合、ピュアブラックテーマを使用します + サービス システムサービスを停止する サービスを再開するには再起動が必要です 実行環境のクリーンアップをしますか? - /data/misc/hide_my_applist_* のシステム サービス キャッシュを削除します。つまり、インターセプトを有効にするには、次回の再起動後にモジュール アプリを手動で 1 回起動する必要があり、インターセプト カウントも消去されます。 - クリーンなランタイム環境を強制する(root) - システム サービスの開始に失敗した場合のみ、それ以外の場合は上記のオプションをクリックする必要があります。 - ランタイムのクリーン + /data/misc/hide_my_applist_* のシステムサービスキャッシュを削除します。つまり、インターセプトを有効にするには、次回の再起動後にモジュールアプリを手動で 1 回起動する必要があり、インターセプトカウントも消去されます。 + クリーンな実行環境を強制する (root) + システムサービスの開始に失敗した場合のみ、それ以外の場合は上記のオプションをクリックする必要があります。 + 実行環境がクリーンアップされました + アップデート アップデートの通知を無効にする ベータ版アップデートを受け取る ベータ版は不安定な場合があります + - 特定のアプリのインストールを検出するのは正しくありませんが、ルートを使用するすべてのアプリがランダムなパッケージ名のサポートを提供するわけではありません。 この場合、検出された root を使用するアプリ (Fake Location や Storage Isolation など) は、検出された root 自体と同じです。\n\n同時に、一部のスマート アプリは、さまざまな抜け穴を使用してアプリ リストを取得し、描画できるようにします。 \n\nこのモジュールは、アプリリストを適切に非表示にしているかどうかをテストする方法をいくつか提供します。 また、Xposed モジュールとして機能して、一部のアプリを非表示にしたり、アプリ リストのリクエストを拒否してプライバシーを保護したりできます。 + + 特定のアプリのインストールを検出することは正しくありませんが、root 権限を使用するすべてのアプリがランダムなパッケージ名に対応しているわけではありません。この場合、root 権限を使用しているアプリ (Fake Location や Storage Isolation など) の検出は、root 権限自体が検出されることと同等です。\n\n同時に、一部のスマートアプリは、さまざまな抜け穴を使用してアプリリストを取得して、ユーザーの行動特性を分析する場合があります。\n\nこのモジュールは、アプリリストを適切に非表示にしているかどうかを検証する方法をいくつか提供します。また、Xposed モジュールとして動作することで、特定のアプリを非表示にしたり、アプリリストの取得要求を拒否したりして、プライバシーを保護することも可能です。 + + + オリジナルの HMA プロジェクトはクローズドソースになり、最新版のコードには一部の重要な変更点が反映されていませんでした。そこで、再びオープンソースとして継続していくために、このフォーク版を作成しました。 + このモジュールの使用方法 - 「テンプレート管理」でテンプレートを作成できます。\n次に、「アプリ管理」でテンプレートをアプリに適用します。 (ターゲットに追加のアプリを選択することもできます。)\nXposed モジュール スコープ/ホワイトリストで「システム フレームワーク」をチェックする必要があります。 - リアルタイムで有効な構成変更。 - デベロッパー + + 「テンプレート管理」でテンプレートを作成できます。\n次に、「アプリ管理」でテンプレートをアプリに適用します。 (ターゲットに追加のアプリを選択することもできます。)\nXposed モジュール スコープ/ホワイトリストで「システム フレームワーク」をチェックする必要があります。 + + + 設定変更はリアルタイムで行われます。 + + 開発者 サポートとフィードバック オープンソースライセンス + 桜 レッド @@ -148,5 +200,10 @@ オレンジ ディープオレンジ ブラウン - グレーブルー + ブルーグレー + + + ダークテーマ + 常にオフ + 常にオン diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 06ac949..3072e42 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -3,52 +3,54 @@ Início Registros Configurações - Gerenciador de modelo - Configurações de modelos - Gerenciador de aplicativos + Gerenciar modelos + Configurações do modelo + Gerenciar apps Selecionar aplicativo Configurações do aplicativo Sobre - Não instale HMAL no usuário secundário - Mostrar registro do crash + Sobre essa versão + Não instale HMA no usuário secundário + Mostrar registro do erro Arquivo de configuração danificado. Por favor limpe os dados do aplicativo Editar lista Deletar - Ligado - Disabled - Modo de trabalho - Lista negra - Lista branca - Aplicação - Preferencias + Habilitado + Desabilitado + Modo de funcionamento + Lista Negra + Lista Branca + Aplicativo + Configurações Recarregar Filtro Sim Não Pesquisar - System + Sistema + Seguir o sistema Módulo ativado [%s] Módulo não ativado Serviço do sistema funcionando [%d] Serviço do sistema não está funcionando - Módulo atualizado, precisa reiniciar - HMAL-OSS foi filtrado total %d requisitos + Módulo atualizado, necessário reiniciar + HMA-OSS filtrou no total %d de pedidos Gerenciar Teste de detecção Baixar aplicativo de teste Desenvolvemos um aplicativo de teste de detecção individual e migramos o teste de detecção para ele. Deseja baixá-lo agora? - Cópia de segurança & Restaurar - Configurações da cópia de segurança - Configurações de restauração + Backup & Restaurar + Backup das configurações + Restaurar backup Importado com sucesso Erro ao importar - Versão do módulo inferior a copia de segurança - Versão da cópia de segurança muito velho - Arquivo da cópia de segurança foi danificado + Versão do módulo inferior ao backup + Versão do backup é muito antiga + Arquivo de backup esta danificado Erro ao exportar - Configuração salvada - Nova atualização acessível: %s + Configuração salva + Nova atualização disponivel: %s Atualização: %s Mostrar apps do sistema @@ -59,97 +61,120 @@ Pela data de instalação Pela data de atualização - Establish templates for \"restless\" apps, controlling applist they can obtain.\n\nBlacklist (recommended): the apps selected in the template will be invisible to the apps applying the template.\nWhitelist: the apps not selected in the template will be invisible to the apps applying the template. - Create a blacklist template - Create a whitelist template - Are you sure to delete the template? - Template name - Name invalid - Delete Template - Template name already exist - Applied to %d apps - %d apps visible - %d apps invisible + Estabalecer modelos para apps \"inquietos\", controlando a lista de aplicativos que eles podem obter.\n\nLista Negra (recomendado): Os apps selecionados no modelo seram invisiveis para os apps no qual esse modelo for aplicado.\nLista Branca: Os apps não selecionados nesse modelo serão invisiveis aos apps no qual esse modelo for aplicado + Criar modelo de lista negra + Criar modelo de lista branca + Tem certeza que quer apagar a configuração + Nome do modelo + Nome inválido + Apagar modelo + Nome de modelo ja existe + "Aplicado a %d aplicativos " + %d aplicativos visiveis + %d aplicativos invisiveis - Enable hide - Template config - Choose template - Exclude system apps - Turn off this option only when really necessary. It is likely to cause crash with it disabled - Using %d templates - %d additional apps visible - %d additional apps invisible + "Ativar escondimento " + Configurações do modelo + Escolher modelo + Escolher perfil + Excluir apps dos sistema + Desative essa opção somente quando REALMENTE necessário, ja que pode ocasionar do app parar de funcionar se desativado + Usando %d modelos + Usando %d perfis de configuração + %d apps adicionais expostos + %d apps adicionais escondidos - Save logs - Clear logs - No logs to save - Logs saved + Opções de desenvolvedor + Salvar registros + Limpar registros + Sem registros pra salvar + Registros salvos - Module - Data isolation - App data: %s\nVold app data: %s\nForce mount: %s - This option is only available on Android 11 and above + Modulo + Isolamento de dados + App data: %s\nVold app data: %s\nForçar montagem: %s + Esta opção só esta disponivel no Android 11 pra cima App data isolation Vold app data isolation - Changing requires root permission - Changing requires reboot - Permission denied - Enforce data isolation for all apps - Data isolation is disabled for apps target API lower than 30 by default - Detail log for debugging - Logger buffer sizes - Hide module icon in launcher - You can still open the app in Xposed manager - Language - Participate in translation - Help us translate %s into your language - Theme - System theme color - Theme color - Dark theme - Pure black dark theme - Use the pure black theme if dark theme is enabled - Service - Stop system service - Need reboot to restart service - Limpar o ambiente do runtime ao mesmo tempo? - Remover arquivo temporário do serviço do sistema em /data/misc/hide_my_applist_*. Isso significa que você precisa iniciar o aplicativo de módulos uma vez manualmente no próximo reinício para habilitar interceptações, e a contagens de interceptações será apagada. - Forçar a limpar o ambiente do runtime (root) - Apenas se o serviço de sistema falhar ao iniciar, por outro lado, você deve clicar acima da opção. - Ambiente do runtime limpado - Atualize - Desabilitar notificações de atualizações + Isso ira ativar um recurso do Android que isola as pastas externas de aplicativos. Isso pode ajudar a melhor esconder apps mas pode causar instabilidade. Esse recurso pode parar de funcionar a qualquer momento ja que esta abandonada no Android. + Alterar requer permissão d root + Alterar requer reinicialização + Permissão negada + Impor isolamento de dados em todos os apps + Isolamento de dados esta desativado para apps q usam API inferior a 30 por padrão + Registros detalhados para depuração + Tamanho do registro + Esconder icone do modulo no launcher + Voce ainda podera abrir o app pelo Lsposed + Nota que o desenvolvedor não é responsavel por problemas causados por apps de risco antes ou depois de voce ativar esse recurso + Idioma + Participe na tradução + Nos ajude a traduzir %s na sua lingua + Tema + Cor de tema do sistema + Cor do tema + Tema escuro + Tema puramente preto + Usar o tema puramente preto se o tema escuro estivar habilitado + Serviço + Parar serviço do sistema + Reinicialização necessario para reiniciar o serviço + Limpar o ambiente de operação ao mesmo tempo? + Remover arquivo temporário do serviço do sistema em /data/misc/hide_my_applist_*. Isso significa que você precisa iniciar o aplicativo manualmente uma vez na próxima reinicialização para habilitar interceptações, e a contagens de interceptações será apagada. + Forçar a limpar o ambiente de operações (root) + Apenas se o serviço de sistema falhar ao iniciar, se não voce deve clicar a opção acima. + Ambiente de operação limpo + Atualizar + Desativar notificações de atualizações Receber atualizações beta A versão beta pode ser instável 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. - How to use this module - You can create templates in \"Template manage\".\nThen apply the templates to apps in \"App manage\". (You can also select extra apps for targets.)\nYou should and ONLY should check \"System Framework\" in Xposed module scope / whitelist. - - Config modification effective in real time. - - Developer - Support and feedback - Open source licenses + Como usar esse modulo + " Voce pode criar modelos em \"Gerenciar modelos\".\nEntão aplique os modelos nos apps em \"Gerenciar app\". (Voce tamber pode selecionar apps extra para esconder de tais.)\nVoce só deve habilitar o \"Sistema Android\" na lista de ativação do Xposed.\n " + " A modificação de configurações tem efeito imediato.\n " + Desenvolvedor + Suporte e ajuda + Licensas de codigo aberto Sakura - Red - Pink - Purple - Deep purple - Indigo - Blue - Light blue - Cyan - Teal - Green - Light green - Lime - Yellow - Amber - Orange - Deep orange - Brown - Blue grey + Vermelho + Rosa + Roxo + Roxo Intenso + Aníl + Azul + Azul Claro + Ciano + Verde-Azulado + Verde + Verde Claro + Lima + Amarelo + Ãmbar + Laranja + Laranaja Intenso + Marrom + Azul Acinzentado + Usando %d perfis + Aplicativo de risco + Esse aplicativo foi marcado como de risco e pode ocasionar em problemas se escondidos dos apps. Deseja continuar? + Esconder + Esconder fontes de instalação + Todos os apps de usuário serão marcados como instalados a partir da Play Store para esse aplicativo + Esconder apps de sistema tambem + Todos os apps de sistema serão marcados como instalados pelo nada para esse applicativo + Ignorar este app + Ative isso se esse app não esta disponivel na Play Store + Não esqueça de fechar o app após salvar a configuração + Modulos Lsposed/Xposed + Gerenciadores de root/Apps de root + Apps de ROM modificado + "Acessibilidade " + Aviso + Ignorar aviso de app de risco + Tema escuro + Sempre desligado + Sempre ligado diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index 3444eb2..e0c683c 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -31,9 +31,9 @@ Mô đun đã kích hoạt [%s] Mô đun ko kích hoạt Dịch vụ HMAL đang chạy [%d] - Dịch vụ HMAL không chạy + Dịch vụ HMAL không chạy. Thử khởi động lại máy? Mô đun đã cập nhật, cần khởi động lại máy - HMAL-OSS đã lọc tổng cộng %d yêu cầu + HMAL-OSS đã lọc tổng cộng %d yêu cầu đọc ứng dụng Quản lý Kiểm thử Tải ứng dụng kiểm thử @@ -59,7 +59,7 @@ Bởi thời gian cài Theo thời gian cập nhật - Establish templates for \"restless\" apps, controlling applist they can obtain.\n\nBlacklist (recommended): the apps selected in the template will be invisible to the apps applying the template.\nWhitelist: the apps not selected in the template will be invisible to the apps applying the template. + Thiết lập mẫu cho các ứng dụng \"không ngừng nghỉ\", kiểm soát danh sách ứng dụng mà chúng có thể nhận được.\n\nDanh sách đen (được đề xuất): các ứng dụng được chọn trong mẫu sẽ ẩn với các ứng dụng áp dụng mẫu.\nDanh sách trắng: các ứng dụng không được chọn trong mẫu sẽ ẩn với các ứng dụng áp dụng mẫu. Tạo một mẫu danh sách đen Tạo một mẫu danh sách trắng Bạn có chắc chắn xóa mẫu không? @@ -83,10 +83,22 @@ Ứng dụng bổ sung hiện hình %d Ứng dụng bổ sung vô hình : %d + Package này có thể gây lỗi + Package này đã được đánh dấu là có khả năng gây lỗi trong ứng dụng được ẩn. Bạn có muốn tiếp tục không? + Giả mạo + Giả mạo nguồn cài đặt app + Tất cả ứng dụng không phải hệ thống sẽ được giả mạo nguồn cài thành từ Google Play + Giả mạo cả ứng dụng hệ thống + Toàn bộ ứng dụng hệ thống sẽ được giả mạo như là đã được cài đặt qua Google Play thay vì là không cho ứng dụng này + Loại trừ app đang ẩn + Bạn có thể bật tùy chọn này nếu ứng dụng đang ẩn không có ở Google Play + Đừng quên buộc dừng app được ẩn sau khi bật tùy chọn này ! + Mô-đun LSPosed/Xposed Các app quản lý root/App root Các app từ custom ROM + Các ứng dụng WebView Quyền trợ năng (Accessibility) @@ -99,16 +111,16 @@ Đã lưu nhật ký Mô đun - Data isolation - App data: %s\nVold app data: %s\nForce mount: %s - This option is only available on Android 11 and above - App data isolation - Vold app data isolation - Changing requires root permission - Changing requires reboot - Permission denied - Enforce data isolation for all apps - Data isolation is disabled for apps target API lower than 30 by default + Cô lập dữ liệu + Dữ liệu ứng dụng: %s\nCô lập nặng: %s\nBuộc mount: %s + Tùy chọn này chỉ khả dụng cho Android 11 trở lên + Cô lập dữ liệu ứng dụng + Cô lập dữ liệu ứng dụng nặng hơn + Yêu cầu quyền ROOT + Thay đổi này cần khởi động lại thiết bị + Quyền bị từ chối + Thực thi cô lập dữ liệu cho tất cả các ứng dụng + Tính năng cô lập dữ liệu bị vô hiệu hóa đối với các ứng dụng có targetSdkVersion thấp hơn 30 theo mặc định Nhật ký chi tiết gỡ lỗi Kích cỡ bộ đệm nhật ký Ẩn mô đun trong Trình khởi chạy @@ -135,12 +147,19 @@ Nhận bản beta Bản beta có thể ko tương thích + + Dự án HideMyAppList đã thành mã nguồn đóng và repository của họ không chứa code của các thay đổi mới trên phiên bản mới hơn, vì thế nên tôi đã tạo ra cái fork này để tiếp tục nó như là mã nguồn mở. + + Về các thay đổi do tôi tự làm + Về fork của frknkrc44 + Tôi KHÔNG thích việc app được đặt tên là HMA-OSS vì nó sẽ gây nhầm lẫn với HMA VPN, vì thế nên tôi đã fork về và tự đổi tên thành theo ý riêng mình. Mặc dù việc phát hiện cài đặt ứng dụng cụ thể là không chính xác, nhưng không phải mọi ứng dụng sử dụng root đều cung cấp hỗ trợ tên gói ngẫu nhiên. Trong trường hợp này, các ứng dụng được phát hiện sử dụng quyền root (chẳng hạn như Fake GPS) tương đương với chính ứng dụng gốc được phát hiện. \n\n Đồng thời, một số ứng dụng thông minh sử dụng nhiều kẽ hở khác nhau để lấy danh sách ứng dụng của bạn, để nó có thể thu hút một cá tính dành cho bạn. \n\nMô-đun này cung cấp một số phương pháp để kiểm tra xem bạn đã ẩn danh sách ứng dụng của mình hay chưa. Ngoài ra, nó có thể hoạt động như một mô-đun Xposed để ẩn một số ứng dụng hoặc từ chối các yêu cầu danh sách ứng dụng để bảo vệ quyền riêng tư của bạn. Làm sao để dùng mô đun này Bạn có thể tạo danh mục trong \"Quản lý danh mục\". \nSau đó, áp dụng cho các ứng dụng trong \"Quản lý ứng dụng\". (Bạn cũng có thể chọn các ứng dụng bổ sung cho các mục tiêu.) \nBạn nên và CHỈ nên kiểm tra \"Khung hệ thống\" trong phạm vi mô-đun Xposed / danh sách trắng. Sửa đổi cấu hình hiệu quả trong thời gian thực. Nhà phát triển + Dev giả [không phải là dev] Giúp và phản hồi Giấy phép nguồn mở diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0428265..b662ed1 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -77,8 +77,18 @@ 排除系统应用 仅在必需时关闭此选项。禁用它很可能会导致崩溃 已启用 %d 个模板 + 使用 %d 预置 + 使用 %d 设置预置 %d 个额外可见的应用 %d 个额外不可见的应用 + 欺骗 + 欺骗安装源 + 用户的所有应用程序都会被伪装成已在 Play Store 上安装了此应用程序 + 欺骗系统应用程序 + 所有系统应用程序都会被伪装成已安装,而非此应用程序 + 排除目标应用 + 如果 Play Store 中没有此应用程序,您可以启用它 + 保存配置后强制停止目标应用程序 保存日志 清除日志 @@ -111,6 +121,8 @@ 深色主题 纯黑主题 当深色主题启用时使用纯黑主题 + 绕过风险软件包警告 + 请注意,对于启用此功能之前或之后出现的与风险软件包相关的问题,开发人员概不负责 服务 停止系统服务 需要重启设备才能再次启动系统服务 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d25a98b..7530e7b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,4 +9,5 @@ 6dp 1dp + 16dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 854e64c..e61bbb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,7 @@ Select apps App settings About - About this fork + About frknkrc44\'s fork Do not install HMAL to secondary user Show crash log @@ -65,6 +65,9 @@ By package name By install time By update time + Try enabling hiding for apps with this template\'s work mode and apps will appear here + No applications found, maybe you didn\'t allow this app to query all packages + No applications added in this preset Establish templates for \"restless\" apps, controlling applist they can obtain.\n\nBlacklist (recommended): the apps selected in the template will be invisible to the apps applying the template.\nWhitelist: the apps not selected in the template will be invisible to the apps applying the template. @@ -91,11 +94,24 @@ Using %d settings presets %d additional apps visible %d additional apps invisible + Risky package + This package is marked as risky and you can cause issues on hidden apps. Do you want to continue? - + Spoofing + Spoof installation sources + All of user apps will be spoofed as installed over Play Store for this application + Spoof system apps too + All of system apps will be spoofed as installed over nothing for this application + Exclude the target app + You can enable it if this app is not available at Play Store + Don\'t forget to force stop the target app after saving the configuration + + + Show presets LSPosed/Xposed modules Root managers/Rooted apps Custom ROM apps + WebView apps Accessibility @@ -125,6 +141,8 @@ Logger buffer sizes Hide module icon in launcher You can still open the app in Xposed manager + Bypass risky package warning + Note that the devs are not responsible for risky package related issues before or after you enabled this feature Language Participate in translation @@ -158,6 +176,8 @@ 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. + About my personal changes + I don\'t like the app being named as HMA-OSS as it will conflict with HMA VPN app so i forked this and changing name to HMAL-OSS myself. 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. @@ -166,6 +186,7 @@ Config modification effective in real time. Developer + Fake developer (tinkerer) Support and feedback Open source licenses diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index 96af57e..11b43c9 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -9,6 +9,28 @@ android:title="@string/app_enable_hide" /> + + + + + + + + @@ -29,22 +51,24 @@ - - - - + android:title="@string/app_spoof"> + + + + + diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index b67f301..421ab63 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -33,6 +33,11 @@ + Unit)? = null companion object { - private var hiddenInstance: AppPresets? = null - - val instance: AppPresets - get() { - if (hiddenInstance == null) { - hiddenInstance = AppPresets() - } - - return hiddenInstance!! - } + val instance by lazy { AppPresets() } } fun getAllPresetNames() = presetList.map { it.name }.toTypedArray() @@ -93,6 +85,7 @@ class AppPresets private constructor() { init { presetList.add(CustomROMPreset()) + presetList.add(WebViewPreset()) presetList.add(RootAppsPreset()) presetList.add(XposedModulesPreset()) } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java index ef8848a..5bbfc05 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.java @@ -9,8 +9,7 @@ public class Constants { public static final String PROVIDER_AUTHORITY = APP_PACKAGE_NAME + ".ServiceProvider"; public static final String GMS_PACKAGE_NAME = "com.google.android.gms"; public static final String GSF_PACKAGE_NAME = "com.google.android.gsf"; - public static final String UPDATE_URL_BASE = "https://api.nullptr.icu/android/hide-my-applist/static/"; - public static final String TRANSLATE_URL = "https://crowdin.com/project/hide-my-applist"; + public static final String VENDING_PACKAGE_NAME = "com.android.vending"; public static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = "persist.zygote.app_data_isolation"; public static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY = "persist.sys.vold_app_data_isolation_enabled"; 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 c60726c..7b9bb01 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt @@ -31,6 +31,9 @@ data class JsonConfig( data class AppConfig( var useWhitelist: Boolean = false, var excludeSystemApps: Boolean = true, + var hideInstallationSource: Boolean = false, + var hideSystemInstallationSource: Boolean = false, + var excludeTargetInstallationSource: Boolean = false, var applyTemplates: MutableSet = mutableSetOf(), var applyPresets: MutableSet = mutableSetOf(), var applySettingsPresets: MutableSet = mutableSetOf(), diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt index 42b8529..85d5be9 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt @@ -8,16 +8,7 @@ class SettingsPresets private constructor() { private val presetList = mutableListOf() companion object { - private var hiddenInstance: SettingsPresets? = null - - val instance: SettingsPresets - get() { - if (hiddenInstance == null) { - hiddenInstance = SettingsPresets() - } - - return hiddenInstance!! - } + val instance by lazy { SettingsPresets() } } fun getAllPresetNames() = presetList.map { it.name }.toTypedArray() 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 4342292..1d7b2a9 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt @@ -76,4 +76,16 @@ object Utils { return false } + + fun endsWithMultiple(source: String, vararg targets: String): Boolean { + assert(source.isNotEmpty() && targets.isNotEmpty()) + + for (target in targets) { + if (source.endsWith(target)) { + return true + } + } + + return false + } } \ No newline at end of file diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/BasePreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/BasePreset.kt index d317c34..43782d7 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/BasePreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/BasePreset.kt @@ -12,6 +12,8 @@ abstract class BasePreset(val name: String) { fun containsPackage(packageName: String) = exactPackageNames.contains(packageName) || packageNames.contains(packageName) + val packages get() = packageNames + exactPackageNames + fun addPackageInfoPreset(appInfo: ApplicationInfo): Boolean { val packageName = appInfo.packageName if (!exactPackageNames.contains(packageName) && canBeAddedIntoPreset(appInfo)) { diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/CustomROMPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/CustomROMPreset.kt index 151405c..9e62392 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/CustomROMPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/CustomROMPreset.kt @@ -4,16 +4,37 @@ import android.content.pm.ApplicationInfo import icu.nullptr.hidemyapplist.common.Utils class CustomROMPreset() : BasePreset("custom_rom") { - override val exactPackageNames = setOf() + override val exactPackageNames = setOf( + "io.chaldeaprjkt.gamespace" + ) override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { val packageName = appInfo.packageName + if (containsPackage(packageName)) { + return false + } + + // LineageOS overlays + if (appInfo.sourceDir.contains("_lineage_")) { + return true + } + // LineageOS apps if (Utils.startsWithMultiple(packageName, "lineageos.", "org.lineageos.")) { return true } + // CAF (CodeAuroraForum) apps + if (packageName.startsWith("com.caf.")) { + return true + } + + // CalyxOS + if (packageName.startsWith("org.calyxos.")) { + return true + } + // AOSPA if (packageName.startsWith("co.aospa.")) { return true @@ -29,6 +50,12 @@ class CustomROMPreset() : BasePreset("custom_rom") { return true } + // EvoX (just added by the community request) + if (Utils.startsWithMultiple("org.evolution.", "org.evolutionx.") || + Utils.endsWithMultiple(".evolution", ".evolutionx")) { + return true + } + // TODO: Add more custom ROM apps return false } 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 f2764f0..920ece3 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 @@ -1,6 +1,7 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo +import icu.nullptr.hidemyapplist.common.Utils import java.util.zip.ZipFile class RootAppsPreset() : BasePreset("root_apps") { @@ -9,7 +10,6 @@ class RootAppsPreset() : BasePreset("root_apps") { "io.github.a13e300.ksuwebui", "io.github.muntashirakon.AppManager", "com.fox2code.mmm", - "xzr.hkf", "com.speedsoftware.rootexplorer", "id.kuato.diskhealth", "com.sunilpaulmathew.debloater", @@ -34,6 +34,8 @@ class RootAppsPreset() : BasePreset("root_apps") { "ru.nsu.bobrofon.easysshfs", "x1125io.initdlight", "com.lonelycatgames.Xplore", + "com.byyoung.setting", + "web1n.stopapp", // kernel managers "flar2.exkernelmanager", @@ -50,6 +52,11 @@ class RootAppsPreset() : BasePreset("root_apps") { return false } + // All libxzr apps (konabess, hkf, ...) + if (Utils.startsWithMultiple(packageName, "xzr.", "moe.xzr.")) { + return true + } + // FX File Manager if (packageName.startsWith("nextapp.fx")) { return true diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/WebViewPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/WebViewPreset.kt new file mode 100644 index 0000000..9aea4f4 --- /dev/null +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/WebViewPreset.kt @@ -0,0 +1,33 @@ +package icu.nullptr.hidemyapplist.common.app_presets + +import android.content.pm.ApplicationInfo + +class WebViewPreset() : BasePreset("webview") { + override val exactPackageNames = setOf( + "com.android.webview", + "com.google.android.webview", + "us.spotco.mulch_wv", + ) + + override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { + val packageName = appInfo.packageName + + if (containsPackage(packageName)) { + return false + } + + // AOSP WebView + if (packageName.startsWith("com.android.webview")) { + return true + } + + if (packageName.startsWith("com.google.android.webview")) { + return true + } + + if (packageName.startsWith("us.spotco.mulch_wv")) { + return true + } + return 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 665d707..65042db 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 @@ -6,6 +6,11 @@ import java.util.zip.ZipFile class XposedModulesPreset() : BasePreset("xposed") { override val exactPackageNames = setOf() + companion object { + private const val XPOSED_PROP1 = "\u0000x\u0000p\u0000o\u0000s\u0000e\u0000d\u0000m\u0000o\u0000d\u0000u\u0000l\u0000e" + private const val XPOSED_PROP2 = "\u0000x\u0000p\u0000o\u0000s\u0000e\u0000d\u0000s\u0000c\u0000o\u0000p\u0000e" + } + override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { ZipFile(appInfo.sourceDir).use { zipFile -> val manifestFile = zipFile.getInputStream( @@ -14,9 +19,9 @@ class XposedModulesPreset() : BasePreset("xposed") { val manifestStr = String(manifestBytes, Charsets.US_ASCII) // Checking with binary because the Android system sucks - if (manifestStr.contains("\u0000x\u0000p\u0000o\u0000s\u0000e\u0000d\u0000m\u0000o\u0000d\u0000u\u0000l\u0000e")) { + if (manifestStr.contains(XPOSED_PROP1) || manifestStr.contains(XPOSED_PROP2)) { return true - } else if (zipFile.getEntry("META-INF/xposed")?.isDirectory ?: false) { + } else if (zipFile.getEntry("META-INF/xposed/module.prop") != null) { return true } } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt index 678a2d2..10e3cbb 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/AccessibilityPreset.kt @@ -14,7 +14,7 @@ class AccessibilityPreset : BasePreset(NAME) { ), ReplacementItem( name = Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, - value = null, + value = "", ), ) } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt index c96ae3c..d2de721 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/DeveloperOptionsPreset.kt @@ -15,6 +15,6 @@ class DeveloperOptionsPreset : BasePreset("dev_options") { ReplacementItem( name = Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, value = "0", - ) + ), ) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5889b09..13c7777 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.12.1" +agp = "8.13.0" kotlin = "2.1.10" material = "1.13.0-rc01" rxhttp = "3.3.1" @@ -13,7 +13,6 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi ksp = { id = "com.google.devtools.ksp", version = "2.1.10-1.0.31" } nav-safeargs-kotlin = { id = "androidx.navigation.safeargs.kotlin", version = "2.8.8" } autoresconfig = { id = "dev.rikka.tools.autoresconfig", version = "1.2.2" } -materialthemebuilder = { id = "dev.rikka.tools.materialthemebuilder", version = "1.5.1" } refine = { id = "dev.rikka.tools.refine", version = "4.4.0" } [libraries] diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt index 0837041..4d0d17e 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.IPackageManager import android.os.Build +import android.os.UserHandle import icu.nullptr.hidemyapplist.common.AppPresets import icu.nullptr.hidemyapplist.common.Constants import icu.nullptr.hidemyapplist.common.IHMAService @@ -11,6 +12,7 @@ import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.Utils import icu.nullptr.hidemyapplist.xposed.hook.AccessibilityHook import icu.nullptr.hidemyapplist.xposed.hook.ActivityHook +import icu.nullptr.hidemyapplist.xposed.hook.ContentProviderHook import icu.nullptr.hidemyapplist.xposed.hook.IFrameworkHook import icu.nullptr.hidemyapplist.xposed.hook.PlatformCompatHook import icu.nullptr.hidemyapplist.xposed.hook.PmsHookTarget28 @@ -154,6 +156,7 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { frameworkHooks.add(ActivityHook(this)) frameworkHooks.add(PmsPackageEventsHook(this)) frameworkHooks.add(AccessibilityHook(this)) + frameworkHooks.add(ContentProviderHook(this)) frameworkHooks.forEach(IFrameworkHook::load) logI(TAG, "Hooks installed") @@ -180,14 +183,37 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { if (query in tpl.appList) return !appConfig.useWhitelist } - for (presetName in appConfig.applyPresets) { - val preset = AppPresets.instance.getPresetByName(presetName) ?: continue - if (preset.containsPackage(query)) return !appConfig.useWhitelist + if (!appConfig.useWhitelist) { + for (presetName in appConfig.applyPresets) { + val preset = AppPresets.instance.getPresetByName(presetName) ?: continue + if (preset.containsPackage(query)) return true + } } return appConfig.useWhitelist } + fun shouldHideInstallationSource(caller: String?, query: String?, user: UserHandle): Int { + if (caller == null || query == null) return 0 + val appConfig = config.scope[caller] ?: return 0 + if (!appConfig.hideInstallationSource) return 0 + logD(TAG, "@shouldHideInstallationSource $caller -> $query") + if (caller == query && appConfig.excludeTargetInstallationSource) return 0 + + try { + val uid = Utils.getPackageUidCompat(pms, query, 0L, user.hashCode()) + logD(TAG, "@shouldHideInstallationSource UID for $caller (${user.hashCode()}) -> $query: $uid") + if (uid < 0) return 0 // invalid package installation source request + } catch (e: Throwable) { + logD(TAG, "@shouldHideInstallationSource UID error for $caller (${user.hashCode()})", e) + return 0 + } + + return if (query in systemApps) { + if (appConfig.hideSystemInstallationSource) { 2 } else { 0 } + } else { 1 } + } + override fun stopService(cleanEnv: Boolean) { logI(TAG, "Stop service") synchronized(loggerLock) { @@ -251,4 +277,7 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { Intent.ACTION_PACKAGE_REMOVED -> AppPresets.instance.handlePackageRemoved(packageName) } } + + override fun getPackagesForPreset(presetName: String) = + AppPresets.instance.getPresetByName(presetName)?.packages?.toTypedArray() } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt deleted file mode 100644 index ac50e15..0000000 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMASettingsProviderService.kt +++ /dev/null @@ -1,148 +0,0 @@ -package icu.nullptr.hidemyapplist.xposed - -import android.content.ContentProvider -import android.os.Bundle -import android.provider.Settings -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.callbacks.XC_LoadPackage -import icu.nullptr.hidemyapplist.common.JsonConfig -import icu.nullptr.hidemyapplist.common.SettingsPresets -import icu.nullptr.hidemyapplist.common.Utils -import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem -import org.frknkrc44.hma_oss.common.BuildConfig -import java.io.File - -class HMASettingsProviderService : IXposedHookLoadPackage { - companion object { - private const val TAG = "SettingsProviderHook" - } - - init { - searchDataDir() - } - - private lateinit var dataDir: String - private lateinit var configFile: File - - private val configLock = Any() - private var configLastModified: Long = 0 - - var config = JsonConfig().apply { detailLog = true } - private set - - private fun searchDataDir() { - File("/data/system").list()?.forEach { - if (it.startsWith("hide_my_applist")) { - if (!this::dataDir.isInitialized) { - val newDir = File("/data/misc/$it") - File("/data/system/$it").renameTo(newDir) - dataDir = newDir.path - } else { - File("/data/system/$it").deleteRecursively() - } - } - } - File("/data/misc").list()?.forEach { - if (it.startsWith("hide_my_applist")) { - if (!this::dataDir.isInitialized) { - dataDir = "/data/misc/$it" - } else if (dataDir != "/data/misc/$it") { - File("/data/misc/$it").deleteRecursively() - } - } - } - if (!this::dataDir.isInitialized) { - dataDir = "/data/misc/hide_my_applist_" + Utils.generateRandomString(16) - } - - File("$dataDir/log").mkdirs() - configFile = File("$dataDir/config.json") - - logI(TAG, "Data dir: $dataDir") - } - - private fun loadConfig() { - if (!configFile.exists()) { - logI(TAG, "Config file not found") - return - } - - if (configLastModified == configFile.lastModified()) { - return - } - - configLastModified = configFile.lastModified() - - val loading = runCatching { - val json = configFile.readText() - JsonConfig.parse(json) - }.getOrElse { - logE(TAG, "Failed to parse config.json", it) - return - } - if (loading.configVersion != BuildConfig.CONFIG_VERSION) { - logW(TAG, "Config version mismatch, need to reload") - return - } - config = loading - logI(TAG, "Config loaded") - } - - override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - logI(TAG, "Load hook") - - findMethod( - "com.android.providers.settings.SettingsProvider", - lpparam.classLoader, - ) { - name == "call" - }.hookBefore { param -> - loadConfig() - - val contentProvider = param.thisObject as ContentProvider - val method = param.args[0] as String? - val name = param.args[1] as String? - - val caller = contentProvider.callingPackage - if (!config.scope.containsKey(caller)) { - // logD(TAG, "Not enabled app: $caller") - return@hookBefore - } - - logD(TAG, "@call caller: $caller, method: $method, name: $name") - - when (method) { - "GET_global", "GET_secure", "GET_system" -> { - val replacement = getSpoofedSetting(caller, name) - if (replacement != null) { - logD(TAG, "@getSettings returned replacement for $caller: ${replacement.value}") - param.result = Bundle().apply { - putString(Settings.NameValueTable.VALUE, replacement.value) - } - } - } - } - } - } - - fun getSpoofedSetting(caller: String?, name: String?): ReplacementItem? { - synchronized(configLock) { - if (caller == null || name == null) return null - - val presets = config.scope[caller]?.applySettingsPresets - if (presets?.isEmpty() ?: true) { - return null - } - - for (presetName in presets) { - val preset = SettingsPresets.instance.getPresetByName(presetName) - val value = preset?.getSpoofedValue(name) - if (value != null) return value - } - - return null - } - } -} \ No newline at end of file diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt index a3f2e44..bc5b388 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/Utils4Xposed.kt @@ -1,7 +1,10 @@ package icu.nullptr.hidemyapplist.xposed +import android.os.Binder import android.os.Build import com.github.kyuubiran.ezxhelper.utils.findField +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.common.Utils class Utils4Xposed { companion object { @@ -12,5 +15,13 @@ class Utils4Xposed { }.get(packageSettings) as? String }.getOrNull() } + + fun getCallingApps(service: HMAService): Array { + val callingUid = Binder.getCallingUid() + 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 index 802b2b2..0764690 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt @@ -3,14 +3,11 @@ 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.getFieldByDesc -import com.github.kyuubiran.ezxhelper.utils.hookAllConstructorAfter 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 kotlin.concurrent.thread private const val TAG = "HMA-XposedEntry" @@ -23,12 +20,7 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { } override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { - if (lpparam.packageName == Constants.APP_PACKAGE_NAME) { - EzXHelperInit.initHandleLoadPackage(lpparam) - hookAllConstructorAfter("icu.nullptr.hidemyapplist.MyApp") { - getFieldByDesc("Licu/nullptr/hidemyapplist/MyApp;->isHooked:Z").setBoolean(it.thisObject, true) - } - } else if (lpparam.packageName == "android") { + if (lpparam.packageName == "android") { EzXHelperInit.initHandleLoadPackage(lpparam) logI(TAG, "Hook entry") @@ -50,8 +42,6 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { } } } - } else if (lpparam.packageName == "com.android.providers.settings") { - HMASettingsProviderService().handleLoadPackage(lpparam) } } } 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 index 67b5737..a3e1360 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AccessibilityHook.kt @@ -1,18 +1,20 @@ package icu.nullptr.hidemyapplist.xposed.hook import android.accessibilityservice.AccessibilityServiceInfo -import android.app.ActivityThread 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.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI +// 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 const val ACCESSIBILITY_SERVICE = "com.android.server.accessibility.AccessibilityManagerService" } private val hookList = mutableSetOf() @@ -20,24 +22,41 @@ class AccessibilityHook(private val service: HMAService) : IFrameworkHook { override fun load() { logI(TAG, "Load hook") - hookList += findMethod( - "com.android.server.accessibility.AccessibilityManagerService" - ) { + hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getInstalledAccessibilityServiceList" }.hookBefore { param -> hookedMethod(param) } - hookList += findMethod( - "com.android.server.accessibility.AccessibilityManagerService" - ) { + hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getEnabledAccessibilityServiceList" }.hookBefore { param -> hookedMethod(param) } + + hookList += findMethod(ACCESSIBILITY_SERVICE) { + name == "addClient" + }.hookBefore { param -> + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + for (caller in callingApps) { + if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { + param.result = 0L + service.filterCount++ + break + } + } + } } private fun hookedMethod(param: XC_MethodHook.MethodHookParam) { try { - val caller = ActivityThread.currentActivityThread().application.packageName - if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { - param.result = java.util.ArrayList() + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return + + for (caller in callingApps) { + if (service.getEnabledSettingsPresets(caller).contains(AccessibilityPreset.NAME)) { + param.result = java.util.ArrayList() + service.filterCount++ + break + } } } catch (e: Throwable) { logE(TAG, "Fatal error occurred, ignore hooks", e) @@ -45,7 +64,7 @@ class AccessibilityHook(private val service: HMAService) : IFrameworkHook { } override fun unload() { - hookList.forEach { it.unhook() } + hookList.forEach(XC_MethodHook.Unhook::unhook) hookList.clear() } } \ No newline at end of file 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 index fbf7e1c..5386be4 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ActivityHook.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ActivityHook.kt @@ -47,6 +47,7 @@ class ActivityHook(private val service: HMAService) : IFrameworkHook { "@executeRequest: insecure query from $caller, target: ${intent?.component}" ) param.result = fakeReturnCode + service.filterCount++ } }.onFailure { logE(TAG, "Fatal error occurred, ignore hook\n", it) 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 new file mode 100644 index 0000000..883c803 --- /dev/null +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt @@ -0,0 +1,75 @@ +package icu.nullptr.hidemyapplist.xposed.hook + +import android.os.Bundle +import android.provider.Settings +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.SettingsPresets +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import icu.nullptr.hidemyapplist.xposed.HMAService +import icu.nullptr.hidemyapplist.xposed.Utils4Xposed +import icu.nullptr.hidemyapplist.xposed.logD + +class ContentProviderHook(private val service: HMAService): IFrameworkHook { + companion object { + private const val TAG = "ContentProviderHook" + } + + private var hook: XC_MethodHook.Unhook? = null + + // Credit: https://github.com/Nitsuya/DoNotTryAccessibility/blob/main/app/src/main/java/io/github/nitsuya/donottryaccessibility/hook/AndroidFrameworkHooker.kt + override fun load() { + hook = findMethod( + "android.content.ContentProvider\$Transport" + ) { + name == "call" + }.hookBefore { param -> + val callingApps = Utils4Xposed.getCallingApps(service) + if (callingApps.isEmpty()) return@hookBefore + + val method = param.args[2] as String? + val name = param.args[3] as String? + + for (caller in callingApps) { + // logD(TAG, "@call caller: $caller, method: $method, name: $name") + + when (method) { + "GET_global", "GET_secure", "GET_system" -> { + val replacement = getSpoofedSetting(caller, name) + if (replacement != null) { + logD(TAG, "@getSettings returned replacement for $caller: ${replacement.value}") + param.result = Bundle().apply { + putString(Settings.NameValueTable.VALUE, replacement.value) + putInt("_generation_index", -1) + } + service.filterCount++ + } + } + } + } + } + } + + fun getSpoofedSetting(caller: String?, name: String?): ReplacementItem? { + if (caller == null || name == null) return null + + val presets = service.getEnabledSettingsPresets(caller) + if (presets.isEmpty()) { + return null + } + + for (presetName in presets) { + val preset = SettingsPresets.instance.getPresetByName(presetName) + val value = preset?.getSpoofedValue(name) + if (value != null) return value + } + + return null + } + + override fun unload() { + hook?.unhook() + hook = null + } +} \ No newline at end of file diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget28.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget28.kt index 211aeb4..12ab1bb 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget28.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget28.kt @@ -4,7 +4,6 @@ import android.content.pm.ResolveInfo 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.common.Constants import icu.nullptr.hidemyapplist.common.Utils import icu.nullptr.hidemyapplist.xposed.HMAService @@ -12,20 +11,21 @@ import icu.nullptr.hidemyapplist.xposed.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI -import java.util.concurrent.atomic.AtomicReference -class PmsHookTarget28(private val service: HMAService) : IFrameworkHook { +class PmsHookTarget28(service: HMAService) : PmsHookTargetBase(service) { companion object { private const val TAG = "PmsHookTarget28" } - private val hooks = mutableListOf() - private var lastFilteredApp: AtomicReference = AtomicReference(null) + // not required until SDK 30 + override val fakeSystemPackageInstallInfo = null + override val fakeUserPackageInstallInfo = 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 -> @@ -52,6 +52,7 @@ class PmsHookTarget28(private val service: HMAService) : IFrameworkHook { unload() } } + hooks += findMethod(service.pms::class.java, findSuper = true) { name == "applyPostResolutionFilter" }.hookAfter { param -> @@ -83,10 +84,7 @@ class PmsHookTarget28(private val service: HMAService) : IFrameworkHook { unload() } } - } - override fun unload() { - hooks.forEach(XC_MethodHook.Unhook::unhook) - hooks.clear() + 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 index 0e6c4f2..7de7691 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget30.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget30.kt @@ -2,31 +2,55 @@ package icu.nullptr.hidemyapplist.xposed.hook 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 de.robv.android.xposed.XC_MethodHook +import com.github.kyuubiran.ezxhelper.utils.paramCount import icu.nullptr.hidemyapplist.common.Constants +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.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI -import java.util.concurrent.atomic.AtomicReference @RequiresApi(Build.VERSION_CODES.R) -class PmsHookTarget30(private val service: HMAService) : IFrameworkHook { +class PmsHookTarget30(service: HMAService) : PmsHookTargetBase(service) { companion object { private const val TAG = "PmsHookTarget30" } - private var hook: XC_MethodHook.Unhook? = null - private var lastFilteredApp: AtomicReference = AtomicReference(null) + override val fakeSystemPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 4 + }.newInstance( + null, + null, + null, + null, + ) + } + + override val fakeUserPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 4 + }.newInstance( + VENDING_PACKAGE_NAME, + psSigningInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + ) + } override fun load() { logI(TAG, "Load hook") - hook = findMethod("com.android.server.pm.AppsFilter") { + hooks += findMethod("com.android.server.pm.AppsFilter") { name == "shouldFilterApplication" }.hookBefore { param -> runCatching { @@ -51,10 +75,7 @@ class PmsHookTarget30(private val service: HMAService) : IFrameworkHook { unload() } } - } - override fun unload() { - hook?.unhook() - hook = null + 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 index c7d1c39..71fe3f9 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget33.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget33.kt @@ -1,21 +1,23 @@ 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 de.robv.android.xposed.XC_MethodHook +import com.github.kyuubiran.ezxhelper.utils.paramCount import icu.nullptr.hidemyapplist.common.Constants +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.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI -import java.util.concurrent.atomic.AtomicReference @RequiresApi(Build.VERSION_CODES.TIRAMISU) -class PmsHookTarget33(private val service: HMAService) : IFrameworkHook { +class PmsHookTarget33(service: HMAService) : PmsHookTargetBase(service) { companion object { private const val TAG = "PmsHookTarget33" @@ -27,13 +29,38 @@ class PmsHookTarget33(private val service: HMAService) : IFrameworkHook { } } - private var hook: XC_MethodHook.Unhook? = null - private var lastFilteredApp: AtomicReference = AtomicReference(null) + override val fakeSystemPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 5 + }.newInstance( + null, + null, + null, + null, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, + ) + } + + override val fakeUserPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 5 + }.newInstance( + VENDING_PACKAGE_NAME, + psSigningInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + PackageInstaller.PACKAGE_SOURCE_STORE, + ) + } @Suppress("UNCHECKED_CAST") override fun load() { logI(TAG, "Load hook") - hook = findMethod("com.android.server.pm.AppsFilterImpl", findSuper = true) { + hooks += findMethod("com.android.server.pm.AppsFilterImpl", findSuper = true) { name == "shouldFilterApplication" }.hookBefore { param -> runCatching { @@ -59,10 +86,7 @@ class PmsHookTarget33(private val service: HMAService) : IFrameworkHook { unload() } } - } - override fun unload() { - hook?.unhook() - hook = null + 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 index 3070c02..5b8de07 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget34.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget34.kt @@ -1,23 +1,25 @@ 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 de.robv.android.xposed.XC_MethodHook +import com.github.kyuubiran.ezxhelper.utils.paramCount import icu.nullptr.hidemyapplist.common.Constants +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.Utils4Xposed import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI -import java.util.concurrent.atomic.AtomicReference @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -class PmsHookTarget34(private val service: HMAService) : IFrameworkHook { +class PmsHookTarget34(service: HMAService) : PmsHookTargetBase(service) { companion object { private const val TAG = "PmsHookTarget34" @@ -29,14 +31,40 @@ class PmsHookTarget34(private val service: HMAService) : IFrameworkHook { } } - private var hook: XC_MethodHook.Unhook? = null - private var exphook: XC_MethodHook.Unhook? = null - private var lastFilteredApp: AtomicReference = AtomicReference(null) + override val fakeSystemPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 6 + }.newInstance( + null, + null, + null, + null, + null, + PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED, + ) + } + + override val fakeUserPackageInstallInfo: Any by lazy { + findConstructor( + "android.content.pm.InstallSourceInfo" + ) { + paramCount == 6 + }.newInstance( + VENDING_PACKAGE_NAME, + psSigningInfo, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + PackageInstaller.PACKAGE_SOURCE_STORE, + ) + } @Suppress("UNCHECKED_CAST") override fun load() { logI(TAG, "Load hook") - hook = findMethod("com.android.server.pm.AppsFilterImpl", findSuper = true) { + hooks += findMethod("com.android.server.pm.AppsFilterImpl", findSuper = true) { name == "shouldFilterApplication" }.hookBefore { param -> runCatching { @@ -64,7 +92,7 @@ class PmsHookTarget34(private val service: HMAService) : IFrameworkHook { } // AOSP exploit - https://github.com/aosp-mirror/platform_frameworks_base/commit/5bc482bd99ea18fe0b4064d486b29d5ae2d65139 // Only 14 QPR2+ has this method - exphook = findMethodOrNull("com.android.server.pm.PackageManagerService", findSuper = true) { + findMethodOrNull("com.android.server.pm.PackageManagerService", findSuper = true) { name == "getArchivedPackageInternal" }?.hookBefore { param -> runCatching { @@ -88,13 +116,10 @@ class PmsHookTarget34(private val service: HMAService) : IFrameworkHook { logE(TAG, "Fatal error occurred, disable hooks", it) unload() } + }?.let { + hooks.add(it) } - } - override fun unload() { - hook?.unhook() - hook = null - exphook?.unhook() - exphook = null + 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 new file mode 100644 index 0000000..b04c5fb --- /dev/null +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt @@ -0,0 +1,88 @@ +package icu.nullptr.hidemyapplist.xposed.hook + +import android.content.pm.PackageManager +import android.os.Binder +import android.os.Build +import com.github.kyuubiran.ezxhelper.utils.findMethod +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.Constants.VENDING_PACKAGE_NAME +import icu.nullptr.hidemyapplist.common.Utils +import icu.nullptr.hidemyapplist.xposed.HMAService +import java.util.concurrent.atomic.AtomicReference + +abstract class PmsHookTargetBase(protected val service: HMAService) : IFrameworkHook { + protected val hooks = mutableListOf() + protected var lastFilteredApp: AtomicReference = AtomicReference(null) + + protected val psSigningInfo by lazy { + Utils.getPackageInfoCompat( + service.pms, + VENDING_PACKAGE_NAME, + PackageManager.GET_SIGNING_CERTIFICATES.toLong(), + 0 + ).signingInfo + } + + abstract val fakeSystemPackageInstallInfo: Any? + abstract val fakeUserPackageInstallInfo: Any? + + override fun load() { + 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 user = Binder.getCallingUserHandle() + val callingUid = Binder.getCallingUid() + if (callingUid == Constants.UID_SYSTEM) return@hookBefore + val callingApps = Utils.binderLocalScope { + service.pms.getPackagesForUid(callingUid) + } ?: return@hookBefore + for (caller in callingApps) { + when (service.shouldHideInstallationSource(caller, query, user)) { + 1 -> param.result = fakeUserPackageInstallInfo + 2 -> param.result = fakeSystemPackageInstallInfo + else -> continue + } + + service.filterCount++ + 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 user = Binder.getCallingUserHandle() + val callingUid = Binder.getCallingUid() + if (callingUid == Constants.UID_SYSTEM) return@hookBefore + val callingApps = Utils.binderLocalScope { + service.pms.getPackagesForUid(callingUid) + } ?: return@hookBefore + for (caller in callingApps) { + when (service.shouldHideInstallationSource(caller, query, user)) { + 1 -> param.result = VENDING_PACKAGE_NAME + 2 -> param.result = null + else -> continue + } + + service.filterCount++ + break + } + } + } + + final override fun unload() { + hooks.forEach(XC_MethodHook.Unhook::unhook) + hooks.clear() + } +}