diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b1e9f43..52d34bf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,6 @@ diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index 415d864..b6c3fd7 100644 Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt index 59ffe27..9e736d7 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ConfigManager.kt @@ -3,7 +3,10 @@ package icu.nullptr.hidemyapplist.service import android.os.Build import android.util.Log import icu.nullptr.hidemyapplist.common.JsonConfig +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem import icu.nullptr.hidemyapplist.hmaApp +import icu.nullptr.hidemyapplist.service.ConfigManager.PTType.APP +import icu.nullptr.hidemyapplist.service.ConfigManager.PTType.SETTINGS import icu.nullptr.hidemyapplist.ui.util.showToast import icu.nullptr.hidemyapplist.util.PackageHelper import org.frknkrc44.hma_oss.R @@ -11,13 +14,26 @@ import org.frknkrc44.hma_oss.common.BuildConfig import java.io.File object ConfigManager { - enum class PresetType { + /** + * Indicates the type of preset/template. + * + * @see APP + * @see SETTINGS + */ + enum class PTType { + /** + * This preset/template type is used for app filtering. + */ APP, + + /** + * This preset/template type is used for settings filtering. + */ SETTINGS, } - data class TemplateInfo(val name: String?, val isWhiteList: Boolean) - data class PresetInfo(val name: String, val type: PresetType?, val translation: String) + data class TemplateInfo(val name: String?, val type: PTType, val isWhiteList: Boolean) + data class PresetInfo(val name: String, val type: PTType?, val translation: String) private const val TAG = "ConfigManager" private lateinit var config: JsonConfig @@ -107,6 +123,14 @@ object ConfigManager { saveConfig() } + var packageQueryWorkaround: Boolean + get() = config.packageQueryWorkaround + set(value) { + config.packageQueryWorkaround = value + saveConfig() + PackageHelper.invalidateCache() + } + fun importConfig(json: String) { config = JsonConfig.parse(json) config.configVersion = BuildConfig.CONFIG_VERSION @@ -118,7 +142,7 @@ object ConfigManager { } fun getTemplateList(): MutableList { - return config.templates.mapTo(mutableListOf()) { TemplateInfo(it.key, it.value.isWhitelist) } + return config.templates.mapTo(mutableListOf()) { TemplateInfo(it.key, PTType.APP, it.value.isWhitelist) } } fun getTemplateAppliedAppList(name: String): ArrayList { @@ -167,6 +191,56 @@ object ConfigManager { saveConfig() } + fun getSettingTemplateList(): MutableList { + return config.settingsTemplates.mapTo(mutableListOf()) { TemplateInfo(it.key, PTType.SETTINGS, false) } + } + + fun getSettingTemplateAppliedAppList(name: String): ArrayList { + return config.scope.mapNotNullTo(ArrayList()) { + if (it.value.applySettingTemplates.contains(name)) it.key else null + } + } + + fun getSettingTemplateTargetSettingList(name: String): ArrayList { + return ArrayList(config.settingsTemplates[name]?.settingsList ?: emptyList()) + } + + fun deleteSettingTemplate(name: String) { + config.scope.forEach { (_, appInfo) -> + appInfo.applySettingTemplates.remove(name) + } + config.settingsTemplates.remove(name) + saveConfig() + } + + fun renameSettingTemplate(oldName: String, newName: String) { + if (oldName == newName) return + config.scope.forEach { (_, appInfo) -> + if (appInfo.applySettingTemplates.contains(oldName)) { + appInfo.applySettingTemplates.remove(oldName) + appInfo.applySettingTemplates.add(newName) + } + } + config.settingsTemplates[newName] = config.settingsTemplates[oldName]!! + config.settingsTemplates.remove(oldName) + saveConfig() + } + + fun updateSettingTemplate(name: String, template: JsonConfig.SettingsTemplate) { + Log.d(TAG, "updateSettingTemplate: $name list = ${template.settingsList}") + config.settingsTemplates[name] = template + saveConfig() + } + + fun updateSettingTemplateAppliedApps(name: String, appliedList: List) { + Log.d(TAG, "updateSettingTemplateAppliedApps: $name list = $appliedList") + config.scope.forEach { (app, appInfo) -> + if (appliedList.contains(app)) appInfo.applySettingTemplates.add(name) + else appInfo.applySettingTemplates.remove(name) + } + saveConfig() + } + fun isHideEnabled(packageName: String): Boolean { return config.scope.containsKey(packageName) } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt index bb8f817..4e80bdd 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/PrefManager.kt @@ -4,6 +4,7 @@ import android.content.ComponentName import android.content.Context.MODE_PRIVATE import android.content.pm.PackageManager import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.edit import icu.nullptr.hidemyapplist.hmaApp import org.frknkrc44.hma_oss.BuildConfig @@ -14,6 +15,7 @@ object PrefManager { private const val PREF_LOCALE = "language" private const val PREF_SYSTEM_WALLPAPER = "system_wallpaper" + private const val PREF_SYSTEM_WALLPAPER_ALPHA = "system_wallpaper_alpha" private const val PREF_DARK_THEME = "dark_theme" private const val PREF_BLACK_DARK_THEME = "black_dark_theme" private const val PREF_FOLLOW_SYSTEM_ACCENT = "follow_system_accent" @@ -36,36 +38,40 @@ object PrefManager { var lastVersion: Int get() = pref.getInt(PREF_LAST_VERSION, 0) - set(value) = pref.edit().putInt(PREF_LAST_VERSION, value).apply() + set(value) = pref.edit { putInt(PREF_LAST_VERSION, value) } var locale: String get() = pref.getString(PREF_LOCALE, "SYSTEM")!! - set(value) = pref.edit().putString(PREF_LOCALE, value).apply() + set(value) = pref.edit { putString(PREF_LOCALE, value) } var darkTheme: Int get() = pref.getInt(PREF_DARK_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) - set(value) = pref.edit().putInt(PREF_DARK_THEME, value).apply() + set(value) = pref.edit { putInt(PREF_DARK_THEME, value) } var systemWallpaper: Boolean get() = pref.getBoolean(PREF_SYSTEM_WALLPAPER, false) - set(value) = pref.edit().putBoolean(PREF_SYSTEM_WALLPAPER, value).apply() + set(value) = pref.edit { putBoolean(PREF_SYSTEM_WALLPAPER, value) } + + var systemWallpaperAlpha: Int + get() = pref.getInt(PREF_SYSTEM_WALLPAPER_ALPHA, 0xAA) + set(value) = pref.edit { putInt(PREF_SYSTEM_WALLPAPER_ALPHA, value) } var blackDarkTheme: Boolean get() = pref.getBoolean(PREF_BLACK_DARK_THEME, false) - set(value) = pref.edit().putBoolean(PREF_BLACK_DARK_THEME, value).apply() + set(value) = pref.edit { putBoolean(PREF_BLACK_DARK_THEME, value) } var followSystemAccent: Boolean get() = pref.getBoolean(PREF_FOLLOW_SYSTEM_ACCENT, true) - set(value) = pref.edit().putBoolean(PREF_FOLLOW_SYSTEM_ACCENT, value).apply() + set(value) = pref.edit { putBoolean(PREF_FOLLOW_SYSTEM_ACCENT, value) } var themeColor: String get() = pref.getString(PREF_THEME_COLOR, "MATERIAL_BLUE")!! - set(value) = pref.edit().putString(PREF_THEME_COLOR, value).apply() + set(value) = pref.edit { putString(PREF_THEME_COLOR, value) } var hideIcon: Boolean get() = pref.getBoolean(PREF_HIDE_ICON, false) set(value) { - pref.edit().putBoolean(PREF_HIDE_ICON, value).apply() + pref.edit { putBoolean(PREF_HIDE_ICON, value) } val component = ComponentName(hmaApp, "${BuildConfig.APPLICATION_ID}.MainActivityLauncher") val status = if (value) PackageManager.COMPONENT_ENABLED_STATE_DISABLED @@ -75,25 +81,25 @@ object PrefManager { var bypassRiskyPackageWarning: Boolean get() = pref.getBoolean(PREF_BYPASS_RISKY_PACKAGE_WARNING, false) - set(value) = pref.edit().putBoolean(PREF_BYPASS_RISKY_PACKAGE_WARNING, value).apply() + set(value) = pref.edit { putBoolean(PREF_BYPASS_RISKY_PACKAGE_WARNING, value) } var appFilter_showSystem: Boolean get() = pref.getBoolean(PREF_APP_FILTER_SHOW_SYSTEM, false) - set(value) = pref.edit().putBoolean(PREF_APP_FILTER_SHOW_SYSTEM, value).apply() + set(value) = pref.edit { putBoolean(PREF_APP_FILTER_SHOW_SYSTEM, value) } var appFilter_sortMethod: SortMethod - get() = SortMethod.values()[pref.getInt(PREF_APP_FILTER_SORT_METHOD, SortMethod.BY_LABEL.ordinal)] - set(value) = pref.edit().putInt(PREF_APP_FILTER_SORT_METHOD, value.ordinal).apply() + get() = SortMethod.entries[pref.getInt(PREF_APP_FILTER_SORT_METHOD, SortMethod.BY_LABEL.ordinal)] + set(value) = pref.edit { putInt(PREF_APP_FILTER_SORT_METHOD, value.ordinal) } var appFilter_reverseOrder: Boolean get() = pref.getBoolean(PREF_APP_FILTER_REVERSE_ORDER, false) - set(value) = pref.edit().putBoolean(PREF_APP_FILTER_REVERSE_ORDER, value).apply() + set(value) = pref.edit { putBoolean(PREF_APP_FILTER_REVERSE_ORDER, value) } var logFilter_level: Int get() = pref.getInt(PREF_LOG_FILTER_LEVEL, 0) - set(value) = pref.edit().putInt(PREF_LOG_FILTER_LEVEL, value).apply() + set(value) = pref.edit { putInt(PREF_LOG_FILTER_LEVEL, value) } var logFilter_reverseOrder: Boolean get() = pref.getBoolean(PREF_LOG_FILTER_REVERSE_ORDER, false) - set(value) = pref.edit().putBoolean(PREF_LOG_FILTER_REVERSE_ORDER, value).apply() + set(value) = pref.edit { putBoolean(PREF_LOG_FILTER_REVERSE_ORDER, value) } } diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt index 1ca1694..ea3203e 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/service/ServiceClient.kt @@ -73,4 +73,13 @@ object ServiceClient : IHMAService, IBinder.DeathRecipient { override fun log(level: Int, tag: String, message: String) { service?.log(level, tag, message) } + + override fun getPackageNames(userId: Int) = service?.getPackageNames(userId) + + override fun getPackageInfo( + packageName: String?, + userId: Int + ) = service?.getPackageInfo(packageName, userId) + + override fun listAllSettings(databaseName: String) = service?.listAllSettings(databaseName) ?: arrayOf() } \ No newline at end of file diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/TemplateAdapter.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/TemplateAdapter.kt index b0881d2..deef82f 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/TemplateAdapter.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/adapter/TemplateAdapter.kt @@ -29,8 +29,13 @@ class TemplateAdapter( fun bind(info: ConfigManager.TemplateInfo) { with(itemView as ListItemView) { setIcon( - if (info.isWhiteList) R.drawable.outline_assignment_24 - else R.drawable.baseline_assignment_24 + when (info.type) { + ConfigManager.PTType.APP -> { + if (info.isWhiteList) R.drawable.outline_assignment_24 + else R.drawable.baseline_assignment_24 + } + ConfigManager.PTType.SETTINGS -> R.drawable.baseline_settings_24 + } ) text = info.name } @@ -54,9 +59,13 @@ class TemplateAdapter( @SuppressLint("NotifyDataSetChanged") fun updateList() { - list = ConfigManager.getTemplateList().apply { + val allItems = (ConfigManager.getTemplateList() + ConfigManager.getSettingTemplateList()).toMutableList() + + list = allItems.apply { sortWith { o1, o2 -> - if (o1.isWhiteList != o2.isWhiteList) { + if (o1.type != o2.type) { + o1.type.compareTo(o2.type) + } else if (o1.isWhiteList != o2.isWhiteList) { o1.isWhiteList.compareTo(o2.isWhiteList) } else { Collator.getInstance(Locale.getDefault()).compare(o1.name, o2.name) 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 c4054a5..0ebb12d 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 @@ -138,7 +138,7 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { adapter.registerAdapterDataObserver( EmptyDataObserver( binding.list, - binding.listEmptyContainer + binding.listEmptyContainer.root ) ) @@ -159,7 +159,7 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -167,7 +167,7 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, 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 c4b6e54..13a2df2 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 @@ -9,6 +9,7 @@ import android.view.WindowInsets import android.widget.Toast import androidx.activity.addCallback import androidx.core.graphics.drawable.toDrawable +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.clearFragmentResultListener import androidx.fragment.app.setFragmentResultListener @@ -35,6 +36,7 @@ import icu.nullptr.hidemyapplist.util.PackageHelper import org.frknkrc44.hma_oss.BuildConfig import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentSettingsBinding +import org.frknkrc44.hma_oss.databinding.LayoutListEmptyBinding class AppSettingsFragment : Fragment(R.layout.fragment_settings) { companion object { @@ -65,11 +67,6 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { saveConfig() } - override fun onDestroyView() { - super.onDestroyView() - saveConfig() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() } setupToolbar( @@ -88,7 +85,7 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -96,7 +93,7 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, @@ -119,6 +116,7 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { "hideSystemInstallationSource" -> pack.config.hideSystemInstallationSource "excludeTargetInstallationSource" -> pack.config.excludeTargetInstallationSource "invertActivityLaunchProtection" -> pack.config.invertActivityLaunchProtection + "excludeVoldIsolation" -> pack.config.excludeVoldIsolation else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -132,6 +130,7 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { "hideSystemInstallationSource" -> pack.config.hideSystemInstallationSource = value "excludeTargetInstallationSource" -> pack.config.excludeTargetInstallationSource = value "invertActivityLaunchProtection" -> pack.config.invertActivityLaunchProtection = value + "excludeVoldIsolation" -> pack.config.excludeVoldIsolation = value else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -147,9 +146,11 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { getString(R.string.app_template_using, pack.config.applyTemplates.size) } - private fun updateApplyPresets() { - findPreference("applyPresets")?.title = - getString(R.string.app_preset_using, pack.config.applyPresets.size) + private fun updateApplyPresets(useWhitelist: Boolean = pack.config.useWhitelist) { + findPreference("applyPresets")?.apply { + isVisible = !useWhitelist + title = getString(R.string.app_preset_using, pack.config.applyPresets.size) + } } private fun updateApplySettingsPresets() { @@ -157,6 +158,11 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { getString(R.string.app_settings_preset_using, pack.config.applySettingsPresets.size) } + private fun updateApplySettingsTemplates() { + findPreference("applySettingsTemplates")?.title = + getString(R.string.app_settings_template_using, pack.config.applySettingTemplates.size) + } + private fun updateExtraAppList(useWhiteList: Boolean) { findPreference("extraAppList")?.title = if (useWhiteList) getString(R.string.app_extra_apps_visible_count, pack.config.extraAppList.size) @@ -225,6 +231,9 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { R.string.app_force_stop_warning, Toast.LENGTH_LONG).show() true } + findPreference("excludeVoldIsolation")?.let { + it.isEnabled = ConfigManager.altVoldAppDataIsolation + } findPreference("invertActivityLaunchProtection")?.let { it.summary = getString(R.string.app_invert_activity_launch_protection_desc) + "\n\n" + getString(R.string.app_global_activity_launch_protection_state, @@ -232,11 +241,14 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { ) } findPreference("useWhiteList")?.setOnPreferenceChangeListener { _, newValue -> + val useWhitelist = newValue as Boolean + pack.config.applyTemplates.clear() pack.config.extraAppList.clear() + pack.config.applyPresets.clear() updateApplyTemplates() - updateApplyPresets() - updateExtraAppList(newValue as Boolean) + updateApplyPresets(useWhitelist) + updateExtraAppList(useWhitelist) true } findPreference("applyTemplates")?.setOnPreferenceClickListener { @@ -246,9 +258,9 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { val checked = templates.map { pack.config.applyTemplates.contains(it) }.toBooleanArray() - MaterialAlertDialogBuilder(requireContext()) + + val dialog = MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.app_choose_template) - .setMultiChoiceItems(templates, checked) { _, i, value -> checked[i] = value } .setNegativeButton(android.R.string.cancel, null) .setPositiveButton(android.R.string.ok) { _, _ -> pack.config.applyTemplates = templates.mapIndexedNotNullTo(mutableSetOf()) { i, name -> @@ -256,7 +268,21 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { } updateApplyTemplates() } - .show() + + if (templates.isNotEmpty()) { + dialog.setMultiChoiceItems(templates, checked) { _, i, value -> + checked[i] = value + } + } else { + val emptyView = LayoutListEmptyBinding.inflate(layoutInflater) + emptyView.root.isVisible = true + emptyView.listEmptyIcon.setImageResource(R.drawable.sentiment_very_dissatisfied_24px) + emptyView.listEmptyText.text = getString(R.string.title_template_manage) + dialog.setView(emptyView.root) + } + + dialog.show() + true } findPreference("applyPresets")?.setOnPreferenceClickListener { @@ -293,6 +319,37 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { .show() true } + findPreference("applySettingsTemplates")?.apply { + setOnPreferenceClickListener { + val templates = ConfigManager.getSettingTemplateList().mapNotNull { it.name }.toTypedArray() + val checked = templates.map { + pack.config.applySettingTemplates.contains(it) + }.toBooleanArray() + + val dialog = MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.app_choose_template) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok) { _, _ -> + pack.config.applySettingTemplates = templates.mapIndexedNotNullTo(mutableSetOf()) { i, name -> + if (checked[i]) name else null + } + updateApplySettingsTemplates() + } + + if (templates.isNotEmpty()) { + dialog.setMultiChoiceItems(templates, checked) { _, i, value -> checked[i] = value } + } else { + val emptyView = LayoutListEmptyBinding.inflate(layoutInflater) + emptyView.root.isVisible = true + emptyView.listEmptyIcon.setImageResource(R.drawable.sentiment_very_dissatisfied_24px) + emptyView.listEmptyText.text = getString(R.string.title_template_manage) + dialog.setView(emptyView.root) + } + + dialog.show() + true + } + } findPreference("applySettingsPresets")?.setOnPreferenceClickListener { val presetNames = SettingsPresets.instance.getAllPresetNames() val presetTranslations = presetNames.map { name -> @@ -343,6 +400,7 @@ class AppSettingsFragment : Fragment(R.layout.fragment_settings) { } updateApplyTemplates() updateApplyPresets() + updateApplySettingsTemplates() updateApplySettingsPresets() updateExtraAppList(pack.config.useWhitelist) } 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 b20fdc3..4d7648f 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 @@ -146,7 +146,7 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -154,7 +154,7 @@ class LogsFragment : Fragment(R.layout.fragment_logs) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, 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 2173173..a26f724 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 @@ -14,6 +14,7 @@ import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SeekBarPreference import androidx.preference.SwitchPreferenceCompat import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.color.DynamicColors @@ -34,6 +35,7 @@ import icu.nullptr.hidemyapplist.util.SuUtils import kotlinx.coroutines.launch import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentSettingsBinding +import org.frknkrc44.hma_oss.ui.activity.BaseActivity import java.util.Locale class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { @@ -60,7 +62,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -68,7 +70,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, @@ -104,6 +106,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen "skipSystemAppDataIsolation" -> ConfigManager.skipSystemAppDataIsolation "disableActivityLaunchProtection" -> ConfigManager.disableActivityLaunchProtection "forceMountData" -> ConfigManager.forceMountData + "packageQueryWorkaround" -> ConfigManager.packageQueryWorkaround else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -118,6 +121,13 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen } } + override fun getInt(key: String, defValue: Int): Int { + return when (key) { + "systemWallpaperAlpha" -> PrefManager.systemWallpaperAlpha + else -> throw IllegalArgumentException("Invalid key: $key") + } + } + override fun putBoolean(key: String, value: Boolean) { when (key) { "followSystemAccent" -> PrefManager.followSystemAccent = value @@ -131,6 +141,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen "appDataIsolation" -> ConfigManager.altAppDataIsolation = value "voldAppDataIsolation" -> ConfigManager.altVoldAppDataIsolation = value "skipSystemAppDataIsolation" -> ConfigManager.skipSystemAppDataIsolation = value + "packageQueryWorkaround" -> ConfigManager.packageQueryWorkaround = value else -> throw IllegalArgumentException("Invalid key: $key") } } @@ -144,6 +155,13 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen else -> throw IllegalArgumentException("Invalid key: $key") } } + + override fun putInt(key: String, value: Int) { + when (key) { + "systemWallpaperAlpha" -> PrefManager.systemWallpaperAlpha = value + else -> throw IllegalArgumentException("Invalid key: $key") + } + } } class DataIsolationPreferenceFragment : PreferenceFragmentCompat() { @@ -289,6 +307,14 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen } } + findPreference("systemWallpaperAlpha")?.apply { + setOnPreferenceChangeListener { _, value -> + (requireActivity() as BaseActivity).applyWallpaperBackgroundColor(value as Int) + + true + } + } + findPreference("clearUninstalledPackageConfigs")?.apply { setOnPreferenceClickListener { MaterialAlertDialogBuilder(requireContext()) @@ -313,9 +339,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen } } } - .setNegativeButton(android.R.string.cancel) { _, _ -> - // do nothing - } + .setNegativeButton(android.R.string.cancel, null) .setCancelable(false) .show() 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 d0308fa..56bd92f 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 @@ -4,13 +4,12 @@ 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.fragment.app.clearFragmentResultListener 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.dialog.MaterialAlertDialogBuilder import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.ui.adapter.TemplateAdapter @@ -19,6 +18,8 @@ import icu.nullptr.hidemyapplist.ui.util.navigate import icu.nullptr.hidemyapplist.ui.util.setupToolbar import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.databinding.FragmentTemplateManageBinding +import org.frknkrc44.hma_oss.ui.fragment.SettingsTemplateConfFragmentArgs +import org.frknkrc44.hma_oss.ui.util.toTargetSettingList class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { @@ -30,16 +31,29 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { toolbar = binding.toolbar, title = getString(R.string.title_template_manage), navigationIcon = R.drawable.baseline_arrow_back_24, - navigationOnClick = { navController.navigateUp() } + navigationOnClick = { navController.navigateUp() }, + menuRes = R.menu.menu_template_manage, + onMenuOptionSelected = { + when (it.itemId) { + R.id.menu_info -> { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.title_template_manage) + .setMessage(R.string.template_usage_hint) + .setNegativeButton(android.R.string.ok, null) + .show() + } + } + } ) - postponeEnterTransition() - view.doOnPreDraw { startPostponedEnterTransition() } binding.newBlacklistTemplate.setOnClickListener { - navigateToSettings(ConfigManager.TemplateInfo(null, false)) + navigateToSettings(ConfigManager.TemplateInfo(null, ConfigManager.PTType.APP, false)) } binding.newWhitelistTemplate.setOnClickListener { - navigateToSettings(ConfigManager.TemplateInfo(null, true)) + navigateToSettings(ConfigManager.TemplateInfo(null, ConfigManager.PTType.APP, true)) + } + binding.newSettingTemplate.setOnClickListener { + navigateToSettings(ConfigManager.TemplateInfo(null, ConfigManager.PTType.SETTINGS, false)) } binding.templateList.layoutManager = LinearLayoutManager(context) binding.templateList.adapter = adapter @@ -47,7 +61,7 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -55,7 +69,7 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, @@ -68,6 +82,13 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { } private fun navigateToSettings(info: ConfigManager.TemplateInfo) { + when (info.type) { + ConfigManager.PTType.APP -> navigateToAppTemplateSettings(info) + ConfigManager.PTType.SETTINGS -> navigateToSettingTemplateSettings(info) + } + } + + private fun navigateToAppTemplateSettings(info: ConfigManager.TemplateInfo) { setFragmentResultListener("template_settings") { _, bundle -> fun deal() { var name = bundle.getString("name") @@ -94,7 +115,42 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) { } val args = TemplateSettingsFragmentArgs(info.name, info.isWhiteList) - val extras = FragmentNavigatorExtras(binding.hintCard to "transition_manage") - navigate(R.id.nav_template_settings, args.toBundle(), extras) + navigate(R.id.nav_template_settings, args.toBundle()) + } + + private fun navigateToSettingTemplateSettings(info: ConfigManager.TemplateInfo) { + setFragmentResultListener("settings_template_conf") { _, bundle -> + fun deal() { + var name = bundle.getString("name") + val appliedList = bundle.getStringArrayList("appliedList")!! + val targetList = bundle.getBundle("settingList")!!.toTargetSettingList() + if (info.name == null) { // New template + if (name.isNullOrEmpty()) return + ConfigManager.updateSettingTemplate( + name, + JsonConfig.SettingsTemplate(targetList.toSet()) + ) + ConfigManager.updateSettingTemplateAppliedApps(name, appliedList) + } else { // Existing template + if (name == null) { + ConfigManager.deleteSettingTemplate(info.name) + } else { + if (name.isEmpty()) name = info.name + if (name != info.name) ConfigManager.renameSettingTemplate(info.name, name) + ConfigManager.updateSettingTemplate( + name, + JsonConfig.SettingsTemplate(targetList.toSet()) + ) + ConfigManager.updateSettingTemplateAppliedApps(name, appliedList) + } + } + } + deal() + adapter.updateList() + clearFragmentResultListener("settings_template_conf") + } + + val args = SettingsTemplateConfFragmentArgs(info.name) + navigate(R.id.nav_setting_template_conf, args.toBundle()) } } 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 6fabf14..dfb5f4a 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 @@ -114,7 +114,7 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -122,7 +122,7 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt index f1db7f0..15722a9 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/util/Fragment.kt @@ -12,7 +12,6 @@ import androidx.appcompat.widget.Toolbar import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.navigation.NavOptions -import androidx.navigation.Navigator import androidx.navigation.fragment.NavHostFragment import org.frknkrc44.hma_oss.R @@ -31,10 +30,6 @@ fun Fragment.navigate(@IdRes resId: Int, args: Bundle? = null) { navController.navigate(resId, args, navOptions) } -fun Fragment.navigate(@IdRes resId: Int, args: Bundle? = null, navExtras: Navigator.Extras?) { - navController.navigate(resId, args, navOptions, navExtras) -} - fun Fragment.setupToolbar( toolbar: Toolbar, title: String, diff --git a/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/ListItemView.kt b/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/ListItemView.kt index f1efdad..2f726e5 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/ListItemView.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/ui/view/ListItemView.kt @@ -19,7 +19,7 @@ class ListItemView @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) init { val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ListItemView, defStyleAttr, defStyleRes) 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 6147f20..a04bb0e 100644 --- a/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt +++ b/app/src/main/java/icu/nullptr/hidemyapplist/util/PackageHelper.kt @@ -6,7 +6,9 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import icu.nullptr.hidemyapplist.common.Constants import icu.nullptr.hidemyapplist.hmaApp +import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.service.PrefManager +import icu.nullptr.hidemyapplist.service.ServiceClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.first @@ -74,14 +76,30 @@ object PackageHelper { isRefreshing.emit(true) val cache = withContext(Dispatchers.IO) { val pm = hmaApp.packageManager - val packages = pm.getInstalledPackages(0) - mutableMapOf().also { - for (packageInfo in packages) { - if (packageInfo.packageName in Constants.packagesShouldNotHide) continue - packageInfo.applicationInfo?.let { appInfo -> - val label = pm.getApplicationLabel(appInfo).toString() - val icon = hmaApp.appIconLoader.loadIcon(appInfo) - it[packageInfo.packageName] = PackageCache(packageInfo, label, icon) + + if (ConfigManager.packageQueryWorkaround) { + val packages = ServiceClient.getPackageNames(0) ?: arrayOf() + mutableMapOf().also { + for (packageName in packages) { + val packageInfo = ServiceClient.getPackageInfo(packageName, 0)!! + if (packageInfo.packageName in Constants.packagesShouldNotHide) continue + packageInfo.applicationInfo?.let { appInfo -> + val label = pm.getApplicationLabel(appInfo).toString() + val icon = hmaApp.appIconLoader.loadIcon(appInfo) + it[packageInfo.packageName] = PackageCache(packageInfo, label, icon) + } + } + } + } else { + val packages = pm.getInstalledPackages(0) + mutableMapOf().also { + for (packageInfo in packages) { + if (packageInfo.packageName in Constants.packagesShouldNotHide) continue + packageInfo.applicationInfo?.let { appInfo -> + val label = pm.getApplicationLabel(appInfo).toString() + val icon = hmaApp.appIconLoader.loadIcon(appInfo) + it[packageInfo.packageName] = PackageCache(packageInfo, label, icon) + } } } } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/activity/BaseActivity.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/activity/BaseActivity.kt index c7b6f08..a3e628d 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/activity/BaseActivity.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/activity/BaseActivity.kt @@ -5,9 +5,11 @@ import android.content.res.Resources import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.drawable.toDrawable import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColorsOptions import icu.nullptr.hidemyapplist.hmaApp +import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.ui.util.ThemeUtils import icu.nullptr.hidemyapplist.util.ConfigUtils.Companion.getLocale @@ -17,6 +19,11 @@ open class BaseActivity : AppCompatActivity() { enableEdgeToEdge() + // I add this manually because the E2E code is not working like I want + // They should give us a separate method to choice it for enableEdgeToEdge + // Source: https://github.com/androidx/androidx/blob/c0f9aabcf6f32029249ac7647711744b68e2a003/activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt#L299 + window.isNavigationBarContrastEnforced = !PrefManager.systemWallpaper + DynamicColors.applyToActivityIfAvailable( this, DynamicColorsOptions.Builder().also { @@ -33,6 +40,20 @@ open class BaseActivity : AppCompatActivity() { } theme.applyStyle(ThemeUtils.getOverlayThemeStyleRes(this), true) + + applyWallpaperBackgroundColor() + } + + fun applyWallpaperBackgroundColor(value: Int = PrefManager.systemWallpaperAlpha) { + if (PrefManager.systemWallpaper) { + val color = (value shl 24) + if (ThemeUtils.isNightMode(this)) { + 0x00000000 + } else { + 0x00FFFFFF + } + + window.setBackgroundDrawable(color.toDrawable()) + } } override fun attachBaseContext(newBase: Context?) { 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 index 37f0701..e944661 100644 --- 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 @@ -30,8 +30,8 @@ class AppPresetListAdapter( } else { setIcon( when (item.type) { - ConfigManager.PresetType.APP -> R.drawable.baseline_assignment_24 - ConfigManager.PresetType.SETTINGS -> R.drawable.baseline_settings_24 + ConfigManager.PTType.APP -> R.drawable.baseline_assignment_24 + ConfigManager.PTType.SETTINGS -> R.drawable.baseline_settings_24 } ) @@ -88,7 +88,7 @@ class AppPresetListAdapter( list += appPresetNames .map { ConfigManager.PresetInfo( it, - ConfigManager.PresetType.APP, + ConfigManager.PTType.APP, appPresetTranslations[appPresetNames.indexOf(it)] ) } .sortedWith { a, b -> a.translation.lowercase().compareTo(b.translation.lowercase()) } @@ -117,7 +117,7 @@ class AppPresetListAdapter( list += settingsPresetNames .map { ConfigManager.PresetInfo( it, - ConfigManager.PresetType.SETTINGS, + ConfigManager.PTType.SETTINGS, settingsPresetTranslations[settingsPresetNames.indexOf(it)] ) } .sortedWith { a, b -> a.translation.lowercase().compareTo(b.translation.lowercase()) } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/BaseSettingsPTAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/BaseSettingsPTAdapter.kt new file mode 100644 index 0000000..f6b63dc --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/BaseSettingsPTAdapter.kt @@ -0,0 +1,40 @@ +package org.frknkrc44.hma_oss.ui.adapter + +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import icu.nullptr.hidemyapplist.ui.view.AppItemView + +abstract class BaseSettingsPTAdapter() : RecyclerView.Adapter() { + abstract val items: List + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = AppItemView(parent.context, false).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) + + override fun getItemCount() = items.size + + abstract fun onItemClick(item: ReplacementItem) + + inner class ViewHolder(view: AppItemView) : RecyclerView.ViewHolder(view) { + fun bind(item: ReplacementItem) { + (itemView as AppItemView).apply { + binding.icon.isVisible = false + binding.label.text = item.name + binding.enabled.isVisible = true + binding.enabled.text = item.database + binding.packageName.text = item.value ?: "null" + binding.root.setOnClickListener { onItemClick(item) } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsPresetListAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsPresetListAdapter.kt index ce43d35..398781e 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsPresetListAdapter.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsPresetListAdapter.kt @@ -1,37 +1,14 @@ package org.frknkrc44.hma_oss.ui.adapter -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView import icu.nullptr.hidemyapplist.common.SettingsPresets import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem -import icu.nullptr.hidemyapplist.ui.view.AppItemView -class SettingsPresetListAdapter(presetName: String) : RecyclerView.Adapter() { - private val presetItems by lazy { - SettingsPresets.instance.getPresetByName(presetName)!!.settingsKVPairs.sortedBy { it.name } +class SettingsPresetListAdapter(name: String) : BaseSettingsPTAdapter() { + override val items by lazy { + SettingsPresets.instance.getPresetByName(name)!!.settingsKVPairs.sortedBy { it.name } } - 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) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(presetItems[position]) - - override fun getItemCount() = presetItems.size - - class ViewHolder(view: AppItemView) : RecyclerView.ViewHolder(view) { - fun bind(item: ReplacementItem) { - (itemView as AppItemView).apply { - binding.icon.isVisible = false - binding.label.text = item.name - binding.packageName.text = item.value ?: "null" - } - } + override fun onItemClick(item: ReplacementItem) { + // do nothing } } \ No newline at end of file diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsTemplateListAdapter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsTemplateListAdapter.kt new file mode 100644 index 0000000..8fd8a35 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/adapter/SettingsTemplateListAdapter.kt @@ -0,0 +1,21 @@ +package org.frknkrc44.hma_oss.ui.adapter + +import android.os.Bundle +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import org.frknkrc44.hma_oss.ui.util.targetSettingListToBundle +import org.frknkrc44.hma_oss.ui.util.toTargetSettingList + +class SettingsTemplateListAdapter( + items: Bundle, + private val onItemClickListener: (SettingsTemplateListAdapter, ReplacementItem) -> Unit +) : BaseSettingsPTAdapter() { + override val items by lazy { + val list = mutableListOf() + list.addAll(items.toTargetSettingList()) + return@lazy list + } + + fun targetSettingListToBundle() = items.targetSettingListToBundle() + + override fun onItemClick(item: ReplacementItem) = onItemClickListener(this, item) +} \ No newline at end of file diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt index f65a8ff..c084b90 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/AboutFragment.kt @@ -2,14 +2,19 @@ package org.frknkrc44.hma_oss.ui.fragment import android.content.Intent import android.content.res.ColorStateList +import android.graphics.BitmapFactory import android.os.Build import android.os.Bundle import android.view.View import android.view.WindowInsets import android.widget.LinearLayout import androidx.annotation.DrawableRes +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.net.toUri +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.transition.AutoTransition +import androidx.transition.TransitionManager import by.kirich1409.viewbindingdelegate.viewBinding import com.bumptech.glide.Glide import icu.nullptr.hidemyapplist.service.PrefManager @@ -31,7 +36,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -41,7 +46,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) { binding.bottomPadding.minimumHeight = barInsets.bottom } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, @@ -53,10 +58,6 @@ class AboutFragment : Fragment(R.layout.fragment_about) { insets } - } - - override fun onStart() { - super.onStart() val tint = ColorStateList.valueOf(homeItemBackgroundColor()) @@ -67,7 +68,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) { } Glide.with(this@AboutFragment) - .load(R.mipmap.ic_launcher_round) + .load(R.mipmap.ic_launcher) .circleCrop() .into(appIcon) @@ -101,11 +102,28 @@ class AboutFragment : Fragment(R.layout.fragment_about) { with(binding.listDeveloper) { backgroundTintList = tint clipToOutline = true + } - // HMA-OSS devs + with(binding.devHeader) { + setOnClickListener { + if (binding.listHma.isVisible) { + binding.expandDevs.animate().rotation(0.0f).start() + } else { + TransitionManager.beginDelayedTransition(binding.listDeveloper, AutoTransition()) + binding.expandDevs.animate().rotation(180.0f).start() + } + + binding.listHma.isVisible = !binding.listHma.isVisible + } + } + + // HMA-OSS devs + with(binding.listHmaOss) { addDevItem(this, R.drawable.cont_fk, "frknkrc44", "HMA-OSS Developer", "https://github.com/frknkrc44") + } - // Original HMA devs + // Original HMA devs + with(binding.listHma) { addDevItem(this, R.drawable.cont_nullptr, "nullptr", "HMA Developer", "https://github.com/Dr-TSNG") addDevItem(this, R.drawable.cont_k, "Ketal", "HMA Collaborator", "https://github.com/keta1") addDevItem(this, R.drawable.cont_aviraxp, "aviraxp", "HMA Collaborator", "https://github.com/aviraxp") @@ -130,10 +148,12 @@ class AboutFragment : Fragment(R.layout.fragment_about) { val newLayout = FragmentAboutListItemBinding.inflate(layoutInflater) setOnClickUrl(newLayout.root, url) - Glide.with(this) - .load(avatarResId) - .circleCrop() - .into(newLayout.aboutPersonIcon) + newLayout.aboutPersonIcon.setImageDrawable(RoundedBitmapDrawableFactory.create( + resources, + BitmapFactory.decodeResource(resources, avatarResId) + ).apply { + isCircular = true + }) newLayout.text1.text = name newLayout.text2.text = desc @@ -142,6 +162,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) { fun addTranslatorItem(layout: LinearLayout, avatarUrl: String, name: String) { val newLayout = FragmentAboutListItemBinding.inflate(layoutInflater) + Glide.with(this) .load(avatarUrl) .placeholder(R.drawable.outline_info_24) 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 index 3ada7a2..b5953ca 100644 --- 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 @@ -16,15 +16,11 @@ class AppPresetFragment() : AppSelectFragment() { override val firstComparator: Comparator = Comparator.comparing(PackageHelper::exists).reversed() - override val adapter by lazy { - val args by navArgs() - AppPresetAdapter(args.presetName) - } + private val args by lazy { navArgs() } - override fun getFragmentTitle(): String { - val args by navArgs() - return args.presetTitle - } + override val adapter by lazy { AppPresetAdapter(args.value.name) } + + override fun getFragmentTitle() = args.value.title override fun sortList() { hmaApp.globalScope.launch { diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BaseSettingsPTFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BaseSettingsPTFragment.kt new file mode 100644 index 0000000..4126076 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/BaseSettingsPTFragment.kt @@ -0,0 +1,73 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.os.Build +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.view.WindowInsets +import androidx.activity.addCallback +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import by.kirich1409.viewbindingdelegate.viewBinding +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.FragmentSettingsPtBaseBinding +import org.frknkrc44.hma_oss.ui.adapter.BaseSettingsPTAdapter + +abstract class BaseSettingsPTFragment : Fragment(R.layout.fragment_settings_pt_base) { + val binding by viewBinding() + + abstract val adapter: BaseSettingsPTAdapter + + abstract val title: String? + + internal open fun onBack() { + navController.navigateUp() + } + + /** + * first - menuRes, second - onMenuOptionSelected + */ + abstract val menu: Pair Unit)>? + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() } + + setupToolbar( + toolbar = binding.toolbar, + title = title ?: "", + navigationIcon = R.drawable.baseline_arrow_back_24, + navigationOnClick = { onBack() }, + menuRes = menu?.first, + onMenuOptionSelected = menu?.second, + ) + + binding.list.layoutManager = LinearLayoutManager(context) + binding.list.adapter = adapter + + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + v.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + v.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/EditSettingFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/EditSettingFragment.kt new file mode 100644 index 0000000..3270377 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/EditSettingFragment.kt @@ -0,0 +1,134 @@ +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.activity.addCallback +import androidx.fragment.app.Fragment +import androidx.fragment.app.setFragmentResult +import androidx.navigation.fragment.navArgs +import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.textfield.MaterialAutoCompleteTextView +import com.google.android.material.textfield.TextInputLayout +import icu.nullptr.hidemyapplist.common.Constants +import icu.nullptr.hidemyapplist.service.ServiceClient +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.FragmentEditSettingBinding + +class EditSettingFragment : Fragment(R.layout.fragment_edit_setting) { + private val binding by viewBinding() + + private val args by navArgs() + + fun saveResult() { + val databaseName = getEditTextValue(binding.databaseSelector) + val settingName = getEditTextValue(binding.settingName) + + if (databaseName != null && settingName != null) { + val settingValue = getEditTextValue(binding.settingValue, false) + val isValueNull = binding.settingValueNull.isChecked + + setFragmentResult("edit_setting", Bundle().apply { + putStringArray( + settingName, + arrayOf( + if (isValueNull) null else (settingValue ?: ""), + databaseName, + ) + ) + }) + } + } + + fun onBack() { + saveResult() + + navController.navigateUp() + } + + fun getEditTextValue(textInputLayout: TextInputLayout, returnNullOnBlank: Boolean = true): String? { + if (textInputLayout.editText?.text.isNullOrBlank()) { + return if (returnNullOnBlank) null else "" + } + + return textInputLayout.editText!!.text.toString() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() } + + setupToolbar( + toolbar = binding.toolbar, + title = getString(R.string.edit), + navigationIcon = R.drawable.baseline_arrow_back_24, + navigationOnClick = { onBack() }, + ) + + with(binding.databaseSelector.editText as MaterialAutoCompleteTextView) { + val values = arrayOf(Constants.SETTINGS_GLOBAL, Constants.SETTINGS_SECURE, Constants.SETTINGS_SYSTEM) + setSimpleItems(values) + setText(args.database) + + if (args.database != null) { + fillSettingNames(args.database.toString()) + } + + setOnItemClickListener { _, _, _, _ -> + fillSettingNames(text.toString()) + } + } + + with(binding.settingName.editText as MaterialAutoCompleteTextView) { + setText(args.name) + } + + with(binding.settingValueNull) { + setOnCheckedChangeListener { _, isChecked -> + binding.settingValue.isEnabled = !isChecked + + if (isChecked) binding.settingValue.editText?.setText("") + } + } + + with(binding.settingValue.editText as MaterialAutoCompleteTextView) { + setText(args.value ?: "") + binding.settingValueNull.isChecked = args.value == null && args.database != null + } + + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + v.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + v.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets + } + } + + fun fillSettingNames(databaseName: String) { + with(binding.settingName) { + isEnabled = true + (editText as MaterialAutoCompleteTextView).setSimpleItems( + ServiceClient.listAllSettings(databaseName) + ) + } + } +} \ 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 faa64ed..f5a4d7e 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 @@ -91,7 +91,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -99,7 +99,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, 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 index d740b9c..46fd978 100644 --- 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 @@ -39,7 +39,7 @@ class PresetManageFragment : Fragment(R.layout.fragment_preset_manage) { binding.root.setOnApplyWindowInsetsListener { v, insets -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) - binding.root.setPadding( + v.setPadding( barInsets.left, barInsets.top, barInsets.right, @@ -47,7 +47,7 @@ class PresetManageFragment : Fragment(R.layout.fragment_preset_manage) { ) } else { @Suppress("deprecation") - binding.root.setPadding( + v.setPadding( insets.systemWindowInsetLeft, insets.systemWindowInsetTop, insets.systemWindowInsetRight, @@ -61,11 +61,11 @@ class PresetManageFragment : Fragment(R.layout.fragment_preset_manage) { private fun navigateToPreset(presetInfo: ConfigManager.PresetInfo) { when (presetInfo.type!!) { - ConfigManager.PresetType.APP -> { + ConfigManager.PTType.APP -> { val args = AppPresetFragmentArgs(presetInfo.name, presetInfo.translation) navigate(R.id.nav_preset_inner_manage, args.toBundle()) } - ConfigManager.PresetType.SETTINGS -> { + ConfigManager.PTType.SETTINGS -> { val args = SettingsPresetFragmentArgs(presetInfo.name, presetInfo.translation) navigate(R.id.nav_settings_preset_inner_manage, args.toBundle()) } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsPresetFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsPresetFragment.kt index c29bd46..313dd4a 100644 --- a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsPresetFragment.kt +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsPresetFragment.kt @@ -1,82 +1,45 @@ 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.fragment.app.Fragment import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.LinearLayoutManager -import by.kirich1409.viewbindingdelegate.viewBinding -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.FragmentSettingsPresetBinding import org.frknkrc44.hma_oss.ui.adapter.SettingsPresetListAdapter /** * Use the [SettingsPresetFragment.newInstance] factory method to * create an instance of this fragment. */ -class SettingsPresetFragment : Fragment(R.layout.fragment_settings_preset) { - private val binding by viewBinding() +class SettingsPresetFragment : BaseSettingsPTFragment() { + private val args by lazy { navArgs() } + + override val adapter by lazy { SettingsPresetListAdapter(args.value.name) } + + override val title by lazy { args.value.title } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - - val args by navArgs() - - setupToolbar( - toolbar = binding.toolbar, - title = args.presetTitle, - navigationIcon = R.drawable.baseline_arrow_back_24, - navigationOnClick = { navController.navigateUp() } - ) - - binding.list.layoutManager = LinearLayoutManager(context) - binding.list.adapter = SettingsPresetListAdapter(args.presetName) - - 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 - } } + override val menu = null + companion object { - private const val ARG_PRESET_NAME = "presetName" - private const val ARG_PRESET_TITLE = "presetTitle" + private const val ARG_PRESET_NAME = "name" + private const val ARG_PRESET_TITLE = "title" /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * - * @param presetName Name of the preset. - * @param presetTitle Title of the preset. + * @param name Name of the preset. + * @param title Title of the preset. * @return A new instance of fragment SettingsPresetFragment. */ @JvmStatic - fun newInstance(presetName: String, presetTitle: String) = + fun newInstance(name: String, title: String) = SettingsPresetFragment().apply { arguments = Bundle().apply { - putString(ARG_PRESET_NAME, presetName) - putString(ARG_PRESET_TITLE, presetTitle) + putString(ARG_PRESET_NAME, name) + putString(ARG_PRESET_TITLE, title) } } } diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt new file mode 100644 index 0000000..f8ec0a8 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateConfFragment.kt @@ -0,0 +1,136 @@ +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.activity.addCallback +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.clearFragmentResultListener +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.setFragmentResultListener +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.navArgs +import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import icu.nullptr.hidemyapplist.service.ConfigManager +import icu.nullptr.hidemyapplist.ui.fragment.ScopeFragmentArgs +import icu.nullptr.hidemyapplist.ui.util.navController +import icu.nullptr.hidemyapplist.ui.util.navigate +import icu.nullptr.hidemyapplist.ui.util.setupToolbar +import kotlinx.coroutines.launch +import org.frknkrc44.hma_oss.R +import org.frknkrc44.hma_oss.databinding.FragmentTemplateSettingsBinding +import org.frknkrc44.hma_oss.ui.util.toTargetSettingList +import org.frknkrc44.hma_oss.ui.viewmodel.SettingsTemplateConfViewModel + +class SettingsTemplateConfFragment : Fragment(R.layout.fragment_template_settings) { + private val binding by viewBinding() + private val viewModel by viewModels { + val args by navArgs() + SettingsTemplateConfViewModel.Factory(args) + } + + private fun onBack(delete: Boolean) { + viewModel.name = viewModel.name?.trim() + if (viewModel.name != viewModel.originalName && (ConfigManager.hasTemplate(viewModel.name) || viewModel.name == null) || delete) { + val builder = MaterialAlertDialogBuilder(requireContext()) + .setTitle(if (delete) R.string.template_delete_title else R.string.template_name_invalid) + .setMessage(if (delete) R.string.template_delete else R.string.template_name_already_exist) + .setPositiveButton(android.R.string.ok) { _, _ -> + saveResult(delete) + } + if (delete) builder.setNegativeButton(android.R.string.cancel, null) + builder.show() + } else { + saveResult(false) + } + } + + private fun saveResult(delete: Boolean) { + setFragmentResult("settings_template_conf", Bundle().apply { + putString("name",if (delete) null else viewModel.name) + putStringArrayList("appliedList", viewModel.appliedAppList.value) + putBundle("settingList", viewModel.targetSettingListToBundle()) + }) + + navController.navigateUp() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack(false) } + setupToolbar( + toolbar = binding.toolbar, + title = getString(R.string.title_settings_template_conf), + navigationIcon = R.drawable.baseline_arrow_back_24, + navigationOnClick = { onBack(false) }, + menuRes = R.menu.menu_delete, + onMenuOptionSelected = { + onBack(true) + } + ) + + binding.templateName.setText(viewModel.name) + (binding.workMode.parent as View).isVisible = false + binding.templateName.addTextChangedListener { viewModel.name = it.toString() } + binding.targetApps.binding.icon.setImageResource(R.drawable.baseline_settings_24) + binding.targetApps.setOnClickListener { + setFragmentResultListener("setting_select") { _, bundle -> + val targetSettings = bundle.toTargetSettingList() + viewModel.targetSettingList.value = targetSettings as ArrayList + clearFragmentResultListener("setting_select") + } + val args = SettingsTemplateInnerFragmentArgs(viewModel.targetSettingListToBundle()) + navigate(R.id.nav_settings_template_inner_manage, args.toBundle()) + } + binding.appliedApps.setOnClickListener { + setFragmentResultListener("app_select") { _, bundle -> + viewModel.appliedAppList.value = bundle.getStringArrayList("checked")!! + clearFragmentResultListener("app_select") + } + val args = ScopeFragmentArgs( + filterOnlyEnabled = true, + isWhiteList = false, + checked = viewModel.appliedAppList.value.toTypedArray() + ) + navigate(R.id.nav_scope, args.toBundle()) + } + + lifecycleScope.launch { + viewModel.targetSettingList.collect { + binding.targetApps.text = String.format(getString(R.string.template_setting_count), it.size) + } + } + lifecycleScope.launch { + viewModel.appliedAppList.collect { + binding.appliedApps.text = String.format(getString(R.string.template_applied_count), it.size) + } + } + + binding.root.setOnApplyWindowInsetsListener { v, insets -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val barInsets = insets.getInsets(WindowInsets.Type.systemBars()) + v.setPadding( + barInsets.left, + barInsets.top, + barInsets.right, + barInsets.bottom, + ) + } else { + @Suppress("deprecation") + v.setPadding( + insets.systemWindowInsetLeft, + insets.systemWindowInsetTop, + insets.systemWindowInsetRight, + insets.systemWindowInsetBottom, + ) + } + + insets + } + } +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateInnerFragment.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateInnerFragment.kt new file mode 100644 index 0000000..4e212c8 --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/fragment/SettingsTemplateInnerFragment.kt @@ -0,0 +1,90 @@ +package org.frknkrc44.hma_oss.ui.fragment + +import android.view.MenuItem +import androidx.fragment.app.clearFragmentResultListener +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.setFragmentResultListener +import androidx.navigation.fragment.navArgs +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import icu.nullptr.hidemyapplist.ui.util.navigate +import org.frknkrc44.hma_oss.R +import org.frknkrc44.hma_oss.ui.adapter.SettingsTemplateListAdapter +import org.frknkrc44.hma_oss.ui.util.toEditSettingFragmentArgs +import org.frknkrc44.hma_oss.ui.util.toTargetSettingList + +class SettingsTemplateInnerFragment : BaseSettingsPTFragment() { + private val args by lazy { navArgs() } + + override val title by lazy { getString(R.string.edit_list) } + + override val adapter by lazy { + SettingsTemplateListAdapter(args.value.items) { adapter, item -> + MaterialAlertDialogBuilder(requireContext()).apply { + setTitle(item.name) + setItems( + R.array.settings_template_inner_action_texts, + ) { dialog, which -> + when (which) { + 0 -> launchEditSettingFragment(item.toEditSettingFragmentArgs()) + 1 -> { + val index = adapter.items.indexOf(item) + if (index >= 0) { + adapter.items.removeAt(index) + adapter.notifyItemRemoved(index) + } + } + } + + dialog.dismiss() + } + }.show() + } + } + + override fun onBack() { + setFragmentResult( + "setting_select", + adapter.targetSettingListToBundle() + ) + + super.onBack() + } + + fun onMenuOptionSelected(item: MenuItem) { + when (item.itemId) { + R.id.menu_add -> { + val args = EditSettingFragmentArgs(database = null, name = "", value = null) + launchEditSettingFragment(args) + } + } + } + + fun launchEditSettingFragment(args: EditSettingFragmentArgs) { + setFragmentResultListener("edit_setting") { _, bundle -> + fun deal() { + val item = bundle.toTargetSettingList().firstOrNull() + + if (item != null) { + val index = adapter.items.indexOfFirst { it.name == item.name && it.database == item.database } + + if (index >= 0) { + adapter.items[index] = item + adapter.notifyItemChanged(index) + } else { + adapter.items.add(item) + adapter.notifyItemInserted(adapter.items.size - 1) + } + } + } + deal() + clearFragmentResultListener("edit_setting") + } + + navigate(R.id.nav_settings_templates_edit_setting, args.toBundle()) + } + + override val menu = Pair( + R.menu.menu_settings_template, + this::onMenuOptionSelected, + ) +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/util/ItemConverter.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/util/ItemConverter.kt new file mode 100644 index 0000000..3dd607a --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/util/ItemConverter.kt @@ -0,0 +1,22 @@ +package org.frknkrc44.hma_oss.ui.util + +import android.os.Bundle +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import org.frknkrc44.hma_oss.ui.fragment.EditSettingFragmentArgs + +fun ReplacementItem.toEditSettingFragmentArgs() = EditSettingFragmentArgs( + database = database, + name = name, + value = value, +) + +fun List.targetSettingListToBundle() = Bundle().apply { + forEach { item -> + putStringArray(item.name, arrayOf(item.value, item.database)) + } +} + +fun Bundle.toTargetSettingList() = keySet().mapNotNull { + val item = getStringArray(it) ?: return@mapNotNull null + ReplacementItem(it, item[0], item[1]) +} diff --git a/app/src/main/java/org/frknkrc44/hma_oss/ui/viewmodel/SettingsTemplateConfViewModel.kt b/app/src/main/java/org/frknkrc44/hma_oss/ui/viewmodel/SettingsTemplateConfViewModel.kt new file mode 100644 index 0000000..6e2e84f --- /dev/null +++ b/app/src/main/java/org/frknkrc44/hma_oss/ui/viewmodel/SettingsTemplateConfViewModel.kt @@ -0,0 +1,36 @@ +package org.frknkrc44.hma_oss.ui.viewmodel + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem +import icu.nullptr.hidemyapplist.service.ConfigManager +import kotlinx.coroutines.flow.MutableStateFlow +import org.frknkrc44.hma_oss.ui.fragment.SettingsTemplateConfFragmentArgs +import org.frknkrc44.hma_oss.ui.util.targetSettingListToBundle + +class SettingsTemplateConfViewModel( + @Suppress("unused") + val originalName: String?, + var name: String? +) : ViewModel() { + + class Factory(private val args: SettingsTemplateConfFragmentArgs) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(SettingsTemplateConfViewModel::class.java)) { + val viewModel = SettingsTemplateConfViewModel(args.name, args.name) + args.name?.let { + viewModel.appliedAppList.value = ConfigManager.getSettingTemplateAppliedAppList(it) + viewModel.targetSettingList.value = ConfigManager.getSettingTemplateTargetSettingList(it) + } + @Suppress("UNCHECKED_CAST") + return viewModel as T + } else throw IllegalArgumentException("Unknown ViewModel class") + } + } + + val appliedAppList = MutableStateFlow>(ArrayList()) + val targetSettingList = MutableStateFlow>(ArrayList()) + + fun targetSettingListToBundle() = targetSettingList.value.targetSettingListToBundle() +} + diff --git a/app/src/main/res/drawable/keyboard_arrow_down_24px.xml b/app/src/main/res/drawable/keyboard_arrow_down_24px.xml new file mode 100644 index 0000000..3f4697d --- /dev/null +++ b/app/src/main/res/drawable/keyboard_arrow_down_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/switch_thumb.xml b/app/src/main/res/drawable/switch_thumb.xml index f74e617..5841dbb 100644 --- a/app/src/main/res/drawable/switch_thumb.xml +++ b/app/src/main/res/drawable/switch_thumb.xml @@ -14,7 +14,7 @@ - + @@ -30,7 +30,7 @@ - + diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index f827918..cdeea33 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,5 +1,6 @@ - + android:layout_height="wrap_content"> + + + + + + + android:layout_height="wrap_content"> + + + + + + - - - - - - + layout="@layout/layout_list_empty" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings_preset.xml b/app/src/main/res/layout/fragment_settings_pt_base.xml similarity index 100% rename from app/src/main/res/layout/fragment_settings_preset.xml rename to app/src/main/res/layout/fragment_settings_pt_base.xml diff --git a/app/src/main/res/layout/fragment_template_manage.xml b/app/src/main/res/layout/fragment_template_manage.xml index 93f7681..d48b222 100644 --- a/app/src/main/res/layout/fragment_template_manage.xml +++ b/app/src/main/res/layout/fragment_template_manage.xml @@ -1,7 +1,6 @@ @@ -24,42 +23,10 @@ android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - - - - - - - - @@ -70,6 +37,13 @@ app:icon="@drawable/baseline_add_24" app:text="@string/template_new_whitelist" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_settings_template.xml b/app/src/main/res/menu/menu_settings_template.xml new file mode 100644 index 0000000..85d9700 --- /dev/null +++ b/app/src/main/res/menu/menu_settings_template.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_template_manage.xml b/app/src/main/res/menu/menu_template_manage.xml new file mode 100644 index 0000000..5a94f2a --- /dev/null +++ b/app/src/main/res/menu/menu_template_manage.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 1a4bfc9..65291b9 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 4ae7d12..0000000 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c9b80df..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png deleted file mode 100644 index b2b8516..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index e46d785..069ca79 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index b6080e8..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 6be4bad..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png deleted file mode 100644 index eaea29f..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 349d52b..e2d91e6 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 7ee3344..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bf2475a..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png deleted file mode 100644 index e3c518a..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 51a676d..26dbf8b 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index f461505..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c9f163d..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png deleted file mode 100644 index 8fb65b9..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index bd4e15d..d74dcf0 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index f345995..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index fef186c..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png deleted file mode 100644 index 4bfd7e8..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index b101495..0b19423 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 2b23a8c..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/navigation/home_nav_graph.xml b/app/src/main/res/navigation/home_nav_graph.xml index be77128..4fda2b5 100644 --- a/app/src/main/res/navigation/home_nav_graph.xml +++ b/app/src/main/res/navigation/home_nav_graph.xml @@ -50,6 +50,18 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index db8e8b3..d65af25 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -75,4 +75,9 @@ @string/app_force_stop_launch @string/app_launch + + + @string/edit + @string/delete + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 626698b..31e61b1 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,6 +7,8 @@ #375634 #8F3232 + #6D6D52 + ?colorPrimary ?colorOnSurfaceInverse ?colorSurfaceContainer diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 7530e7b..f8aa354 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,5 +9,7 @@ 6dp 1dp + 4dp + 8dp 16dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d6e4d43..6011d8f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ Settings Manage templates Template settings + Settings template configuration Manage apps Select apps App settings @@ -31,6 +32,8 @@ No Search System + Add + Edit Follow system @@ -73,12 +76,14 @@ 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 + Create a settings template Are you sure to delete the template? Template name Name invalid Delete Template Template name already exist Applied to %d apps + %d settings added %d apps visible %d apps invisible @@ -92,6 +97,7 @@ Using %d templates Using %d presets Using %d settings presets + Using %d settings templates %d additional apps visible %d additional apps invisible Risky package @@ -120,10 +126,18 @@ Custom ROM apps Suspicious apps Detector/Checker apps + Shizuku/Dhizuku apps Accessibility Developer options + Input method + + + Settings database + Setting name + Setting value + NULL Save logs @@ -161,6 +175,8 @@ Clear uninstalled app configs This option allows you to create a config without remnants but you\'ll have to reconfigure apps when you installed them again. This action cannot be undone. + Alternative package query + Don\'t enable this option if you see the package list in this app correctly. Language Participate in translation diff --git a/app/src/main/res/values/themes_overlay.xml b/app/src/main/res/values/themes_overlay.xml index c3ca1c8..dae0333 100644 --- a/app/src/main/res/values/themes_overlay.xml +++ b/app/src/main/res/values/themes_overlay.xml @@ -23,7 +23,6 @@ diff --git a/app/src/main/res/xml/app_settings.xml b/app/src/main/res/xml/app_settings.xml index bd381ca..78d201e 100644 --- a/app/src/main/res/xml/app_settings.xml +++ b/app/src/main/res/xml/app_settings.xml @@ -55,6 +55,15 @@ android:summary="@string/app_invert_activity_launch_protection_desc" /> + + + + @@ -66,6 +75,10 @@ android:icon="@drawable/outline_assignment_24" android:key="applyPresets" tools:title="@string/app_preset_using" /> + + + () - var loggerFunction: ((String) -> Unit)? = null + private val manifestDataCache = mutableMapOf() + + var loggerFunction: ((Int, String) -> Unit)? = null companion object { val instance by lazy { AppPresets() } } + fun readManifest(packageName: String, zipFile: ZipFile): String { + var cache = manifestDataCache[packageName] + if (cache == null) { + loggerFunction?.invoke(Log.DEBUG, "@readManifest cache is null, reading manifest for $packageName") + + val manifestFile = zipFile.getInputStream( + zipFile.getEntry("AndroidManifest.xml") + ) + val manifestBytes = manifestFile.use { it.readBytes() } + cache = String(manifestBytes, Charsets.US_ASCII) + manifestDataCache[packageName] = cache + } else { + loggerFunction?.invoke(Log.DEBUG, "@readManifest returning cache for $packageName") + } + + return cache + } + fun getAllPresetNames() = presetList.map { it.name }.toTypedArray() // fun filterPresetsByName(names: Array) = presetList.filter { names.contains(it.name) } fun getPresetByName(name: String) = presetList.firstOrNull { it.name == name } - fun reloadPresetsIfEmpty(pms: IPackageManager) { + fun reloadPresets(pms: IPackageManager) { + presetList.forEach { it.clearPackageList() } + val appsList = getInstalledApplicationsCompat(pms, 0, 0) for (appInfo in appsList) { runCatching { - appHasGMSConnection(appInfo, appInfo.packageName, loggerFunction) + tryToAddIntoGMSConnectionList(appInfo, appInfo.packageName) { + loggerFunction?.invoke(Log.DEBUG, it) + } }.onFailure { fail -> - loggerFunction?.invoke(fail.toString()) + loggerFunction?.invoke(Log.ERROR, fail.toString()) } presetList.forEach { runCatching { it.addPackageInfoPreset(appInfo) }.onFailure { fail -> - loggerFunction?.invoke(fail.toString()) + loggerFunction?.invoke(Log.ERROR, fail.toString()) } } } - presetList.forEach { - loggerFunction?.invoke(it.toString()) - } + presetList.forEach { loggerFunction?.invoke(Log.DEBUG, it.toString()) } + + manifestDataCache.clear() } fun handlePackageAdded(pms: IPackageManager, packageName: String) { + if (presetList.any { it.containsPackage(packageName) }) { + return + } + var appInfo: ApplicationInfo? = null var addedInAList = false @@ -62,11 +93,11 @@ class AppPresets private constructor() { if (appInfo != null) { runCatching { if (it.addPackageInfoPreset(appInfo!!)) { - loggerFunction?.invoke("Package $packageName added into ${it.name}!") + loggerFunction?.invoke(Log.DEBUG, "Package $packageName added into ${it.name}!") addedInAList = true } }.onFailure { fail -> - loggerFunction?.invoke(fail.toString()) + loggerFunction?.invoke(Log.ERROR, fail.toString()) } } } @@ -76,10 +107,14 @@ class AppPresets private constructor() { appInfo = getPackageInfoCompat(pms, packageName, 0, 0).applicationInfo if (appInfo != null) - appHasGMSConnection(appInfo, packageName, loggerFunction) + tryToAddIntoGMSConnectionList(appInfo, packageName) { + loggerFunction?.invoke(Log.DEBUG, it) + } if (addedInAList) - loggerFunction?.invoke("Package add event handled for $packageName!") + loggerFunction?.invoke(Log.DEBUG, "Package add event handled for $packageName!") + + manifestDataCache.clear() } fun handlePackageRemoved(packageName: String) { @@ -93,7 +128,7 @@ class AppPresets private constructor() { ignoredForRiskyPackagesList.remove(packageName) if (itWasInAList) - loggerFunction?.invoke("Package remove event handled for $packageName!") + loggerFunction?.invoke(Log.DEBUG, "Package remove event handled for $packageName!") } init { @@ -101,6 +136,7 @@ class AppPresets private constructor() { presetList.add(RootAppsPreset()) presetList.add(XposedModulesPreset()) presetList.add(SuspiciousAppsPreset()) + presetList.add(SDhizukuAppsPreset()) presetList.add(DetectorAppsPreset()) } } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt index e0e8274..d1cc3b1 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Constants.kt @@ -4,8 +4,8 @@ import org.frknkrc44.hma_oss.common.BuildConfig object Constants { const val PROVIDER_AUTHORITY = "${BuildConfig.APP_PACKAGE_NAME}.ServiceProvider" - private const val GMS_PACKAGE_NAME = "com.google.android.gms" - private const val GSF_PACKAGE_NAME = "com.google.android.gsf" + const val GMS_PACKAGE_NAME = "com.google.android.gms" + const val GSF_PACKAGE_NAME = "com.google.android.gsf" const val VENDING_PACKAGE_NAME = "com.android.vending" const val TRANSLATE_URL = "https://crowdin.com/project/frknkrc44-hma-oss" @@ -14,6 +14,20 @@ object Constants { val gmsPackages = arrayOf(GMS_PACKAGE_NAME, GSF_PACKAGE_NAME) val riskyPackages = arrayOf(VENDING_PACKAGE_NAME) + gmsPackages + const val SETTINGS_GLOBAL = "global" + const val SETTINGS_SYSTEM = "system" + const val SETTINGS_SECURE = "secure" + + const val SETTINGS_TYPE_GLOBAL: Int = 0 + const val SETTINGS_TYPE_SYSTEM: Int = 1 + const val SETTINGS_TYPE_SECURE: Int = 2 + // const val SETTINGS_TYPE_SSAID: Int = 3 + // const val SETTINGS_TYPE_CONFIG: Int = 4 + + const val FAKE_INSTALLATION_SOURCE_DISABLED = 0 + const val FAKE_INSTALLATION_SOURCE_USER = 1 + const val FAKE_INSTALLATION_SOURCE_SYSTEM = 2 + val packagesShouldNotHide = setOf( "android", "android.media", 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 acd11b0..47065da 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/JsonConfig.kt @@ -1,14 +1,10 @@ package icu.nullptr.hidemyapplist.common +import icu.nullptr.hidemyapplist.common.settings_presets.ReplacementItem import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.frknkrc44.hma_oss.common.BuildConfig -private val encoder = Json { - encodeDefaults = true - ignoreUnknownKeys = true -} - @Serializable data class JsonConfig( var configVersion: Int = BuildConfig.CONFIG_VERSION, @@ -19,7 +15,9 @@ data class JsonConfig( var altAppDataIsolation: Boolean = false, var altVoldAppDataIsolation: Boolean = false, var skipSystemAppDataIsolation: Boolean = true, + var packageQueryWorkaround: Boolean = false, val templates: MutableMap = mutableMapOf(), + val settingsTemplates: MutableMap = mutableMapOf(), val scope: MutableMap = mutableMapOf() ) { @Serializable @@ -30,6 +28,13 @@ data class JsonConfig( override fun toString() = encoder.encodeToString(this) } + @Serializable + data class SettingsTemplate( + val settingsList: Set + ) { + override fun toString() = encoder.encodeToString(this) + } + @Serializable data class AppConfig( var useWhitelist: Boolean = false, @@ -38,8 +43,10 @@ data class JsonConfig( var hideSystemInstallationSource: Boolean = false, var excludeTargetInstallationSource: Boolean = false, var invertActivityLaunchProtection: Boolean = false, + var excludeVoldIsolation: Boolean = false, var applyTemplates: MutableSet = mutableSetOf(), var applyPresets: MutableSet = mutableSetOf(), + var applySettingTemplates: MutableSet = mutableSetOf(), var applySettingsPresets: MutableSet = mutableSetOf(), var extraAppList: MutableSet = mutableSetOf() ) { @@ -48,6 +55,11 @@ data class JsonConfig( companion object { fun parse(json: String) = encoder.decodeFromString(json) + + private val encoder = Json { + encodeDefaults = true + ignoreUnknownKeys = true + } } override fun toString() = encoder.encodeToString(this) diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/RiskyPackageUtils.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/RiskyPackageUtils.kt index 9c51e3a..0a35b4e 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/RiskyPackageUtils.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/RiskyPackageUtils.kt @@ -11,28 +11,20 @@ object RiskyPackageUtils { fun appHasGMSConnection(query: String) = query in ignoredForRiskyPackagesList - internal fun appHasGMSConnection(appInfo: ApplicationInfo, query: String, loggerFunction: ((String) -> Unit)?): Boolean { - if (query in ignoredForRiskyPackagesList) return true + internal fun tryToAddIntoGMSConnectionList(appInfo: ApplicationInfo, packageName: String, loggerFunction: ((String) -> Unit)?) { + if (packageName in ignoredForRiskyPackagesList) return try { ZipFile(appInfo.sourceDir).use { zipFile -> - val manifestFile = zipFile.getInputStream( - zipFile.getEntry("AndroidManifest.xml") - ) - val manifestBytes = manifestFile.use { it.readBytes() } - val manifestStr = String(manifestBytes, Charsets.US_ASCII) + val manifestStr = AppPresets.instance.readManifest(packageName, zipFile) // Checking with binary because the Android system sucks if (manifestStr.contains(GMS_PROP) || manifestStr.contains(FIREBASE_PROP)) { - if (ignoredForRiskyPackagesList.add(query)) { - loggerFunction?.invoke("@appHasGMSConnection $query added in ignored packages list") + if (ignoredForRiskyPackagesList.add(packageName)) { + loggerFunction?.invoke("@appHasGMSConnection $packageName added in ignored packages list") } - - return true } } } catch (_: Throwable) { } - - return false } } \ No newline at end of file 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 85d5be9..4302c06 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/SettingsPresets.kt @@ -3,6 +3,7 @@ package icu.nullptr.hidemyapplist.common import icu.nullptr.hidemyapplist.common.settings_presets.AccessibilityPreset import icu.nullptr.hidemyapplist.common.settings_presets.BasePreset import icu.nullptr.hidemyapplist.common.settings_presets.DeveloperOptionsPreset +import icu.nullptr.hidemyapplist.common.settings_presets.InputMethodPreset class SettingsPresets private constructor() { private val presetList = mutableListOf() @@ -12,11 +13,11 @@ class SettingsPresets private constructor() { } fun getAllPresetNames() = presetList.map { it.name }.toTypedArray() - // fun filterPresetsByName(names: Array) = presetList.filter { names.contains(it.name) } fun getPresetByName(name: String) = presetList.firstOrNull { it.name == name } init { presetList.add(DeveloperOptionsPreset()) presetList.add(AccessibilityPreset()) + presetList.add(InputMethodPreset()) } } 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 1572f2c..522f41b 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/Utils.kt @@ -28,6 +28,14 @@ object Utils { return result } + fun getInstalledPackagesCompat(pms: IPackageManager, flags: Long, userId: Int): List { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + pms.getInstalledPackages(flags, userId) + } else { + pms.getInstalledPackages(flags.toInt(), userId) + }.list + } + fun getInstalledApplicationsCompat(pms: IPackageManager, flags: Long, userId: Int): List { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { pms.getInstalledApplications(flags, userId) 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 bd02e62..7d0f98e 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 @@ -13,6 +13,8 @@ abstract class BasePreset(val name: String) { val packages get() = packageNames + exactPackageNames + fun clearPackageList() = packageNames.clear() + fun addPackageInfoPreset(appInfo: ApplicationInfo): Boolean { val packageName = appInfo.packageName if (!containsPackage(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 c112019..d1a83ca 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 @@ -3,7 +3,11 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo import icu.nullptr.hidemyapplist.common.Utils -class CustomROMPreset : BasePreset("custom_rom") { +class CustomROMPreset : BasePreset(NAME) { + companion object { + const val NAME = "custom_rom" + } + override val exactPackageNames = setOf( "io.chaldeaprjkt.gamespace", "powersaver.pro", @@ -13,7 +17,7 @@ class CustomROMPreset : BasePreset("custom_rom") { val packageName = appInfo.packageName // LineageOS overlays - if (appInfo.sourceDir.contains("_lineage_")) { + if (Utils.containsMultiple(appInfo.sourceDir, "_lineage", "lineage_")) { return true } @@ -78,6 +82,11 @@ class CustomROMPreset : BasePreset("custom_rom") { return true } + // Xiaomi.EU apps + if (packageName.startsWith("eu.xiaomi.")) { + return true + } + // TODO: Add more custom ROM apps return false } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt index 404bb56..2695fcb 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/DetectorAppsPreset.kt @@ -2,7 +2,11 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo -class DetectorAppsPreset : BasePreset("detector_apps") { +class DetectorAppsPreset : BasePreset(NAME) { + companion object { + const val NAME = "detector_apps" + } + override val exactPackageNames = setOf( // Detector apps "com.reveny.nativecheck", 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 0e744fa..eafeded 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 @@ -4,7 +4,11 @@ import android.content.pm.ApplicationInfo import icu.nullptr.hidemyapplist.common.Utils import java.util.zip.ZipFile -class RootAppsPreset : BasePreset("root_apps") { +class RootAppsPreset : BasePreset(NAME) { + companion object { + const val NAME = "root_apps" + } + override val exactPackageNames = setOf( // rooted apps "io.github.a13e300.ksuwebui", @@ -40,6 +44,7 @@ class RootAppsPreset : BasePreset("root_apps") { "me.twrp.twrpapp", "com.slash.batterychargelimit", "com.valhalla.thor", + "me.itejo443.bindhosts", // Scene's "Core Edition" cannot be detected in the Xposed preset "com.omarea.vtools", diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SDhizukuAppsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SDhizukuAppsPreset.kt new file mode 100644 index 0000000..7f4f1f8 --- /dev/null +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SDhizukuAppsPreset.kt @@ -0,0 +1,37 @@ +package icu.nullptr.hidemyapplist.common.app_presets + +import android.content.pm.ApplicationInfo +import icu.nullptr.hidemyapplist.common.AppPresets +import icu.nullptr.hidemyapplist.common.Utils +import java.util.zip.ZipFile + +class SDhizukuAppsPreset : BasePreset(NAME) { + companion object { + const val NAME = "shizuku_dhizuku" + const val DHIZUKU_PROVIDER = "\u0000c\u0000o\u0000m\u0000.\u0000r\u0000o\u0000s\u0000a\u0000n\u0000.\u0000d\u0000h\u0000i\u0000z\u0000u\u0000k\u0000u\u0000.\u0000s\u0000e\u0000r\u0000v\u0000e\u0000r\u0000.\u0000p\u0000r\u0000o\u0000v\u0000i\u0000d\u0000e\u0000r" + const val SHIZUKU_PROVIDER = "\u0000r\u0000i\u0000k\u0000k\u0000a\u0000.\u0000s\u0000h\u0000i\u0000z\u0000u\u0000k\u0000u\u0000.\u0000S\u0000h\u0000i\u0000z\u0000u\u0000k\u0000u\u0000P\u0000r\u0000o\u0000v\u0000i\u0000d\u0000e\u0000r" + } + + override val exactPackageNames = setOf( + "com.rosan.dhizuku" + ) + + override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { + val packageName = appInfo.packageName + + // All Shizuku apps + if (packageName.startsWith("moe.shizuku.")) { + return true + } + + ZipFile(appInfo.sourceDir).use { zipFile -> + val manifestStr = AppPresets.instance.readManifest(packageName, zipFile) + + if (Utils.containsMultiple(manifestStr, SHIZUKU_PROVIDER, DHIZUKU_PROVIDER)) { + return true + } + } + + return false + } +} \ No newline at end of file diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SuspiciousAppsPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SuspiciousAppsPreset.kt index 919ccfd..bbd3aa3 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SuspiciousAppsPreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/app_presets/SuspiciousAppsPreset.kt @@ -3,13 +3,16 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo import java.util.zip.ZipFile -class SuspiciousAppsPreset : BasePreset("sus_apps") { +class SuspiciousAppsPreset : BasePreset(NAME) { + companion object { + const val NAME = "sus_apps" + } + override val exactPackageNames = setOf( "com.reveny.vbmetafix.service", "berserker.android.apps.sshdroid", "com.iamaner.oneclickfreeze", "com.shamanland.privatescreenshots", - "com.aistra.hail", // Remote desktop apps "com.devolutions.remotedesktopmanager", @@ -57,11 +60,6 @@ class SuspiciousAppsPreset : BasePreset("sus_apps") { return true } - // All Shizuku apps - if (packageName.startsWith("moe.shizuku.")) { - return true - } - // All RealVNC apps (categorized as suspicious, because some of apps checking for them) if (packageName.startsWith("com.realvnc.")) { return true 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 7c7a078..5abfd87 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 @@ -3,7 +3,11 @@ package icu.nullptr.hidemyapplist.common.app_presets import android.content.pm.ApplicationInfo import java.util.zip.ZipFile -class XposedModulesPreset : BasePreset("xposed") { +class XposedModulesPreset : BasePreset(NAME) { + companion object { + const val NAME = "xposed" + } + override val exactPackageNames = setOf() override fun canBeAddedIntoPreset(appInfo: ApplicationInfo): Boolean { 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 10e3cbb..9a60286 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 @@ -1,6 +1,7 @@ package icu.nullptr.hidemyapplist.common.settings_presets import android.provider.Settings +import icu.nullptr.hidemyapplist.common.Constants class AccessibilityPreset : BasePreset(NAME) { companion object { @@ -11,10 +12,12 @@ class AccessibilityPreset : BasePreset(NAME) { ReplacementItem( name = Settings.Secure.ACCESSIBILITY_ENABLED, value = "0", + Constants.SETTINGS_SECURE, ), ReplacementItem( name = Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, value = "", + Constants.SETTINGS_SECURE, ), ) } diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/BasePreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/BasePreset.kt index bfbdee3..29c679c 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/BasePreset.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/BasePreset.kt @@ -1,14 +1,6 @@ package icu.nullptr.hidemyapplist.common.settings_presets abstract class BasePreset(val name: String) { - /* - companion object { - const val SETTINGS_GLOBAL = "global" - const val SETTINGS_SYSTEM = "system" - const val SETTINGS_SECURE = "secure" - } - */ - abstract val settingsKVPairs: List fun getSpoofedValue(key: String) = settingsKVPairs.firstOrNull { it.name == key } 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 53f4540..dc90e68 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 @@ -1,8 +1,10 @@ package icu.nullptr.hidemyapplist.common.settings_presets import android.provider.Settings +import icu.nullptr.hidemyapplist.common.Constants class DeveloperOptionsPreset : BasePreset("dev_options") { + @Suppress("DEPRECATION") override val settingsKVPairs = listOf( ReplacementItem( Settings.Global.ADB_ENABLED, @@ -16,6 +18,15 @@ class DeveloperOptionsPreset : BasePreset("dev_options") { Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, "0", ), + ReplacementItem( + "hidden_api_policy", + null, + ), + ReplacementItem( + Settings.Secure.ALLOW_MOCK_LOCATION, + "0", + Constants.SETTINGS_SECURE, + ), /* ReplacementItem( "debug.force_rtl", diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/InputMethodPreset.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/InputMethodPreset.kt new file mode 100644 index 0000000..2faae7d --- /dev/null +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/InputMethodPreset.kt @@ -0,0 +1,23 @@ +package icu.nullptr.hidemyapplist.common.settings_presets + +import android.provider.Settings +import icu.nullptr.hidemyapplist.common.Constants + +class InputMethodPreset : BasePreset(NAME) { + companion object { + const val NAME = "input_method" + } + + override val settingsKVPairs = listOf( + ReplacementItem( + Settings.Secure.DEFAULT_INPUT_METHOD, + "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME", + Constants.SETTINGS_SECURE, + ), + ReplacementItem( + Settings.Secure.TTS_DEFAULT_SYNTH, + "com.google.android.tts", + Constants.SETTINGS_SECURE, + ), + ) +} \ No newline at end of file diff --git a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/ReplacementItem.kt b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/ReplacementItem.kt index 784e7a7..9792ad1 100644 --- a/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/ReplacementItem.kt +++ b/common/src/main/java/icu/nullptr/hidemyapplist/common/settings_presets/ReplacementItem.kt @@ -1,13 +1,17 @@ package icu.nullptr.hidemyapplist.common.settings_presets +import icu.nullptr.hidemyapplist.common.Constants +import kotlinx.serialization.Serializable + +@Serializable data class ReplacementItem( val name: String, val value: String?, - // val database: String = BasePreset.SETTINGS_GLOBAL, + val database: String = Constants.SETTINGS_GLOBAL, ) { override fun toString() = "ReplacementItem { " + "'name': '$name', " + "'value': '$value', " + // "'database': '$database'" + " }" -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 415b007..8e05b18 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,4 @@ android.experimental.enableNewResourceShrinker.preciseShrinking=true -android.enableAppCompileTimeRClass=true android.useAndroidX=true -android.disableMinifyLocalDependenciesForLibraries=false android.enableJetifier=true org.gradle.jvmargs=-Xmx2048m "-XX:MaxMetaspaceSize=1024m" 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 dfff0ce..0a7b560 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/HMAService.kt @@ -5,13 +5,16 @@ import android.content.pm.ApplicationInfo import android.content.pm.IPackageManager import android.os.Build import android.os.UserHandle +import android.provider.Settings import android.util.Log +import com.github.kyuubiran.ezxhelper.utils.isStatic import icu.nullptr.hidemyapplist.common.AppPresets import icu.nullptr.hidemyapplist.common.Constants import icu.nullptr.hidemyapplist.common.IHMAService import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.RiskyPackageUtils.appHasGMSConnection import icu.nullptr.hidemyapplist.common.Utils +import icu.nullptr.hidemyapplist.common.app_presets.DetectorAppsPreset import icu.nullptr.hidemyapplist.xposed.hook.AccessibilityHook import icu.nullptr.hidemyapplist.xposed.hook.ActivityHook import icu.nullptr.hidemyapplist.xposed.hook.AppDataIsolationHook @@ -30,7 +33,7 @@ import java.io.File import java.util.concurrent.ExecutorService import java.util.concurrent.Executors -class HMAService(val pms: IPackageManager) : IHMAService.Stub() { +class HMAService(val pms: IPackageManager, val pmn: Any?) : IHMAService.Stub() { companion object { private const val TAG = "HMA-Service" @@ -70,8 +73,8 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { instance = this loadConfig() installHooks() - AppPresets.instance.loggerFunction = { logD(TAG, it) } - AppPresets.instance.reloadPresetsIfEmpty(pms) + AppPresets.instance.loggerFunction = { level, msg -> logWithLevel(level, TAG, msg) } + AppPresets.instance.reloadPresets(pms) logI(TAG, "HMA service initialized") } @@ -170,7 +173,18 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { logI(TAG, "Hooks installed") } - fun isHookEnabled(packageName: String) = config.scope.containsKey(packageName) + fun isHookEnabled(packageName: String?) = config.scope.containsKey(packageName) + + fun isAppDataIsolationExcluded(packageName: String?): Boolean { + if (packageName.isNullOrBlank()) return false + + return config.scope[packageName]?.excludeVoldIsolation ?: false + } + + fun getEnabledSettingsTemplates(caller: String?): Set { + if (caller == null) return setOf() + return config.scope[caller]?.applySettingTemplates ?: return setOf() + } fun getEnabledSettingsPresets(caller: String?): Set { if (caller == null) return setOf() @@ -201,8 +215,17 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { if (!appConfig.useWhitelist) { for (presetName in appConfig.applyPresets) { val preset = AppPresets.instance.getPresetByName(presetName) ?: continue - if (preset.containsPackage(query)) - return !isAppInGMSIgnoredPackages(caller, query) + + if (preset.containsPackage(query)) { + // Do not hide detector apps from Play Store if they are connected to GMS + val overriddenCaller = if (presetName == DetectorAppsPreset.NAME && caller == Constants.VENDING_PACKAGE_NAME) { + Constants.GMS_PACKAGE_NAME + } else { + caller + } + + return !isAppInGMSIgnoredPackages(overriddenCaller, query) + } } } @@ -210,14 +233,12 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { } fun shouldHideActivityLaunch(caller: String?, query: String?): Boolean { - if (shouldHide(caller, query)) { - val appConfig = config.scope[caller] - if (appConfig != null) { - return if (appConfig.invertActivityLaunchProtection) { - config.disableActivityLaunchProtection - } else { - !config.disableActivityLaunchProtection - } + val appConfig = config.scope[caller] + if (appConfig != null && shouldHide(caller, query)) { + return if (appConfig.invertActivityLaunchProtection) { + config.disableActivityLaunchProtection + } else { + !config.disableActivityLaunchProtection } } @@ -225,25 +246,31 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { } fun shouldHideInstallationSource(caller: String?, query: String?, user: UserHandle): Int { - if (caller == null || query == null) return 0 - if (caller == BuildConfig.APP_PACKAGE_NAME) return 0 - val appConfig = config.scope[caller] ?: return 0 - if (!appConfig.hideInstallationSource) return 0 + if (caller == null || query == null) return Constants.FAKE_INSTALLATION_SOURCE_DISABLED + if (caller == BuildConfig.APP_PACKAGE_NAME) return Constants.FAKE_INSTALLATION_SOURCE_DISABLED + val appConfig = config.scope[caller] ?: return Constants.FAKE_INSTALLATION_SOURCE_DISABLED + if (!appConfig.hideInstallationSource) return Constants.FAKE_INSTALLATION_SOURCE_DISABLED logD(TAG, "@shouldHideInstallationSource $caller: $query") - if (caller == query && appConfig.excludeTargetInstallationSource) return 0 + if (caller == query && appConfig.excludeTargetInstallationSource) return Constants.FAKE_INSTALLATION_SOURCE_DISABLED 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 + if (uid < 0) return Constants.FAKE_INSTALLATION_SOURCE_DISABLED // invalid package installation source request } catch (e: Throwable) { logD(TAG, "@shouldHideInstallationSource UID error for $caller, ${user.hashCode()}", e) - return 0 + return Constants.FAKE_INSTALLATION_SOURCE_DISABLED } return if (query in systemApps) { - if (appConfig.hideSystemInstallationSource) { 2 } else { 0 } - } else { 1 } + if (appConfig.hideSystemInstallationSource) { + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM + } else { + Constants.FAKE_INSTALLATION_SOURCE_DISABLED + } + } else { + Constants.FAKE_INSTALLATION_SOURCE_USER + } } override fun stopService(cleanEnv: Boolean) { @@ -333,4 +360,30 @@ class HMAService(val pms: IPackageManager) : IHMAService.Stub() { override fun log(level: Int, tag: String, message: String) { logWithLevel(level, tag, message) } + + override fun getPackageNames(userId: Int) = Utils.binderLocalScope { + Utils.getInstalledPackagesCompat(pms, 0L, userId).map { it.packageName }.toTypedArray() + } + + override fun getPackageInfo( + packageName: String, + userId: Int + ) = Utils.binderLocalScope { + Utils.getPackageInfoCompat(pms, packageName, 0L, userId) + } + + override fun listAllSettings(databaseName: String): Array { + val settingClass = when (databaseName) { + Constants.SETTINGS_GLOBAL -> Settings.Global::class.java + Constants.SETTINGS_SECURE -> Settings.Secure::class.java + Constants.SETTINGS_SYSTEM -> Settings.System::class.java + else -> throw IllegalArgumentException("Invalid database name $databaseName") + } + + val readableVariables = settingClass.declaredFields.mapNotNull { field -> + if (field.isStatic && field.type.simpleName == "String") field.get(null) as String else null + } + + return readableVariables.toTypedArray() + } } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt index 64a993d..8c1294a 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/UserService.kt @@ -52,9 +52,9 @@ object UserService { } } - fun register(pms: IPackageManager) { + fun register(pms: IPackageManager, pmn: Any?) { logI(TAG, "Initialize HMAService - Version ${BuildConfig.APP_VERSION_NAME}") - val service = HMAService(pms) + val service = HMAService(pms, pmn) try { appUid = Utils.getPackageUidCompat(service.pms, BuildConfig.APP_PACKAGE_NAME, 0, 0) 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 0764690..2e90d20 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/XposedEntry.kt @@ -14,6 +14,8 @@ private const val TAG = "HMA-XposedEntry" @Suppress("unused") class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { + var targetsLeft = mutableListOf("package", "package_native") + var targetStorage = mutableMapOf() override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) { EzXHelperInit.initZygote(startupParam) @@ -28,13 +30,31 @@ class XposedEntry : IXposedHookZygoteInit, IXposedHookLoadPackage { serviceManagerHook = findMethod("android.os.ServiceManager") { name == "addService" }.hookBefore { param -> - if (param.args[0] == "package") { + val name = param.args[0] as String + if (targetsLeft.contains(name)) { + when (name) { + "package", "package_native" -> { + targetStorage[name] = param.args[1] + targetsLeft.remove(name) + } + else -> { + // skip if there is no package_native available + if (targetStorage.containsKey("package")) { + targetsLeft.remove("package_native") + } + } + } + } + + if (targetsLeft.isEmpty()) { serviceManagerHook?.unhook() - val pms = param.args[1] as IPackageManager - logD(TAG, "Got pms: $pms") + val pms = targetStorage["package"] as IPackageManager + val pmn = targetStorage["package_native"] + logD(TAG, "Got pms: $pms, $pmn") thread { runCatching { - UserService.register(pms) + UserService.register(pms, pmn) + targetStorage.clear() logI(TAG, "User service started") }.onFailure { logE(TAG, "System service crashed", it) 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 a3e1360..e7b364f 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,12 +1,14 @@ package icu.nullptr.hidemyapplist.xposed.hook import android.accessibilityservice.AccessibilityServiceInfo +import android.content.pm.ParceledListSlice import com.github.kyuubiran.ezxhelper.utils.findMethod import com.github.kyuubiran.ezxhelper.utils.hookBefore import de.robv.android.xposed.XC_MethodHook import icu.nullptr.hidemyapplist.common.settings_presets.AccessibilityPreset import icu.nullptr.hidemyapplist.xposed.HMAService import icu.nullptr.hidemyapplist.xposed.Utils4Xposed +import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI @@ -24,11 +26,11 @@ class AccessibilityHook(private val service: HMAService) : IFrameworkHook { hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getInstalledAccessibilityServiceList" - }.hookBefore { param -> hookedMethod(param) } + }.hookBefore { param -> hookedMethod(param, true) } hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "getEnabledAccessibilityServiceList" - }.hookBefore { param -> hookedMethod(param) } + }.hookBefore { param -> hookedMethod(param, false) } hookList += findMethod(ACCESSIBILITY_SERVICE) { name == "addClient" @@ -46,14 +48,23 @@ class AccessibilityHook(private val service: HMAService) : IFrameworkHook { } } - private fun hookedMethod(param: XC_MethodHook.MethodHookParam) { + private fun hookedMethod(param: XC_MethodHook.MethodHookParam, returnParcel: Boolean) { try { 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() + val returnedList = java.util.ArrayList() + + logD(TAG, "@${param.method.name} returned empty list for $caller") + + param.result = if (returnParcel) { + ParceledListSlice(returnedList) + } else { + returnedList + } + service.filterCount++ break } diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt index dde9af6..2286493 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/AppDataIsolationHook.kt @@ -13,7 +13,7 @@ import icu.nullptr.hidemyapplist.xposed.HMAService import icu.nullptr.hidemyapplist.xposed.logD import icu.nullptr.hidemyapplist.xposed.logE import icu.nullptr.hidemyapplist.xposed.logI - +import org.frknkrc44.hma_oss.common.BuildConfig @RequiresApi(Build.VERSION_CODES.R) class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { @@ -36,7 +36,7 @@ class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { "com.android.server.am.ProcessList" ) { name == "startProcess" - }?.hookAfter { param -> + }?.hookBefore { param -> if (service.config.altAppDataIsolation) { val isEnabled = XposedHelpers.getBooleanField( param.thisObject, @@ -55,8 +55,7 @@ class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { } if (service.config.altVoldAppDataIsolation && !voldHookSkipped) { - val fuseEnabled = Build.VERSION.SDK_INT > Build.VERSION_CODES.R || - SystemProperties.getBoolean(FUSE_PROP, false) + val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) if (!fuseEnabled) { voldHookSkipped = true @@ -87,19 +86,53 @@ class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { ) { name == "needsStorageDataIsolation" }?.hookAfter { param -> - if (service.config.altVoldAppDataIsolation && service.config.skipSystemAppDataIsolation) { + if (service.config.altVoldAppDataIsolation) { val app = param.args.find { it.javaClass.simpleName == "ProcessRecord" } val uid = XposedHelpers.getIntField(app, "uid") + val processName = runCatching { + XposedHelpers.getObjectField(app, "processName") + }.getOrDefault("") + val mountNode = runCatching { + XposedHelpers.getIntField(app, "mMountMode") + }.getOrDefault(0) + val isolated = runCatching { + XposedHelpers.getBooleanField(app, "isolated") + }.getOrDefault(false) + val appZygote = runCatching { + XposedHelpers.getBooleanField(app, "appZygote") + }.getOrDefault(false) + val apps = Utils.binderLocalScope { service.pms.getPackagesForUid(uid) } ?: return@hookAfter - val isSystemApp = service.systemApps.any { apps.contains(it) } logD( TAG, - "@needsStorageDataIsolation $uid - ${apps.contentToString()} isSystemApp: $isSystemApp" + "@needsStorageDataIsolation $uid and ${apps.contentToString()} - $processName value without override: ${param.result}, mount node: $mountNode, isolated: $isolated, appZygote: $appZygote" ) - if (isSystemApp) param.result = false + + // Do not isolate this module for safety + if (apps.contains(BuildConfig.APP_PACKAGE_NAME)) { + param.result = false + return@hookAfter + } + + if (apps.any { service.isAppDataIsolationExcluded(it) }) { + param.result = false + } + + if (service.config.skipSystemAppDataIsolation) { + val isSystemApp = service.systemApps.any { apps.contains(it) } + logD( + TAG, + "@needsStorageDataIsolation $uid and ${apps.contentToString()} - isSystemApp: $isSystemApp" + ) + + if (isSystemApp) { + param.result = false + return@hookAfter + } + } } }?.let { hooks += it @@ -112,8 +145,7 @@ class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { }?.hookBefore { param -> runCatching { if (service.config.altVoldAppDataIsolation) { - val fuseEnabled = Build.VERSION.SDK_INT > Build.VERSION_CODES.R || - SystemProperties.getBoolean(FUSE_PROP, false) + val fuseEnabled = SystemProperties.getBoolean(FUSE_PROP, false) if (!fuseEnabled) { logE(TAG, "StorageManagerService - FUSE storage is not enabled, disable hooks") @@ -165,7 +197,7 @@ class AppDataIsolationHook(private val service: HMAService): IFrameworkHook { } ?: continue for (app in apps) { - if (app in service.systemApps) { + if (app in service.systemApps || app == BuildConfig.APP_PACKAGE_NAME) { logD( TAG, "@remountAppStorageDirs SYSTEM $pid - $packageName is marked to remove" 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 index 883c803..3095345 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/ContentProviderHook.kt @@ -10,6 +10,7 @@ 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 +import icu.nullptr.hidemyapplist.xposed.logI class ContentProviderHook(private val service: HMAService): IFrameworkHook { companion object { @@ -21,7 +22,7 @@ class ContentProviderHook(private val service: HMAService): IFrameworkHook { // 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" + $$"android.content.ContentProvider$Transport" ) { name == "call" }.hookBefore { param -> @@ -32,13 +33,16 @@ class ContentProviderHook(private val service: HMAService): IFrameworkHook { val name = param.args[3] as String? for (caller in callingApps) { - // logD(TAG, "@call caller: $caller, method: $method, name: $name") + if (!service.isHookEnabled(caller)) continue + + logD(TAG, "@spoofSettings received caller: $caller, method: $method, name: $name") when (method) { "GET_global", "GET_secure", "GET_system" -> { - val replacement = getSpoofedSetting(caller, name) + val database = method.substring(method.indexOf('_') + 1) + val replacement = getSpoofedSetting(caller, name, database) if (replacement != null) { - logD(TAG, "@getSettings returned replacement for $caller: ${replacement.value}") + logI(TAG, "@spoofSettings $name in $database replaced for $caller") param.result = Bundle().apply { putString(Settings.NameValueTable.VALUE, replacement.value) putInt("_generation_index", -1) @@ -51,18 +55,22 @@ class ContentProviderHook(private val service: HMAService): IFrameworkHook { } } - fun getSpoofedSetting(caller: String?, name: String?): ReplacementItem? { + fun getSpoofedSetting(caller: String?, name: String?, database: String): ReplacementItem? { if (caller == null || name == null) return null - val presets = service.getEnabledSettingsPresets(caller) - if (presets.isEmpty()) { - return null + val templates = service.getEnabledSettingsTemplates(caller) + val replacement = service.config.settingsTemplates.firstNotNullOfOrNull { (key, value) -> + if (key in templates) value.settingsList.firstOrNull { it.name == name } else null } + if (replacement != null) return replacement - for (presetName in presets) { - val preset = SettingsPresets.instance.getPresetByName(presetName) - val value = preset?.getSpoofedValue(name) - if (value != null) return value + val presets = service.getEnabledSettingsPresets(caller) + if (presets.isNotEmpty()) { + for (presetName in presets) { + val preset = SettingsPresets.instance.getPresetByName(presetName) + val replacement = preset?.getSpoofedValue(name) + if (replacement?.database == database) return replacement + } } return null 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 12ab1bb..cc50017 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 @@ -19,8 +19,11 @@ class PmsHookTarget28(service: HMAService) : PmsHookTargetBase(service) { } // not required until SDK 30 - override val fakeSystemPackageInstallInfo = null - override val fakeUserPackageInstallInfo = null + /* + override val fakeUserPackageInstallSource = null + */ + override val fakeSystemPackageInstallSourceInfo = null + override val fakeUserPackageInstallSourceInfo = null @Suppress("UNCHECKED_CAST") override fun 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 caccaa7..ca94788 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 @@ -24,7 +24,24 @@ class PmsHookTarget30(service: HMAService) : PmsHookTargetBase(service) { private const val TAG = "PmsHookTarget30" } - override val fakeSystemPackageInstallInfo: Any by lazy { + /* + override val fakeUserPackageInstallSource: Any by lazy { + findConstructor( + "com.android.server.pm.InstallSource" + ) { + true + }.newInstance( + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + VENDING_PACKAGE_NAME, + false, + false, + psSigningDetails, + ) + } + */ + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { @@ -37,7 +54,7 @@ class PmsHookTarget30(service: HMAService) : PmsHookTargetBase(service) { ) } - override val fakeUserPackageInstallInfo: Any by lazy { + override val fakeUserPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { diff --git a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt index 6442771..747ae02 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTarget31.kt @@ -24,7 +24,11 @@ class PmsHookTarget31(service: HMAService) : PmsHookTargetBase(service) { private const val TAG = "PmsHookTarget31" } - override val fakeSystemPackageInstallInfo: Any by lazy { + /* + override val fakeUserPackageInstallSource = null + */ + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { @@ -37,7 +41,7 @@ class PmsHookTarget31(service: HMAService) : PmsHookTargetBase(service) { ) } - override val fakeUserPackageInstallInfo: Any by lazy { + override val fakeUserPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { 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 71fe3f9..d0846ea 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 @@ -29,7 +29,11 @@ class PmsHookTarget33(service: HMAService) : PmsHookTargetBase(service) { } } - override val fakeSystemPackageInstallInfo: Any by lazy { + /* + override val fakeUserPackageInstallSource = null + */ + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { @@ -43,7 +47,7 @@ class PmsHookTarget33(service: HMAService) : PmsHookTargetBase(service) { ) } - override val fakeUserPackageInstallInfo: Any by lazy { + override val fakeUserPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { 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 5b8de07..f89f40a 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 @@ -31,7 +31,11 @@ class PmsHookTarget34(service: HMAService) : PmsHookTargetBase(service) { } } - override val fakeSystemPackageInstallInfo: Any by lazy { + /* + override val fakeUserPackageInstallSource = null + */ + + override val fakeSystemPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { @@ -46,7 +50,7 @@ class PmsHookTarget34(service: HMAService) : PmsHookTargetBase(service) { ) } - override val fakeUserPackageInstallInfo: Any by lazy { + override val fakeUserPackageInstallSourceInfo: Any by lazy { findConstructor( "android.content.pm.InstallSourceInfo" ) { 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 index 29373a6..10cd7fd 100644 --- a/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt +++ b/xposed/src/main/java/icu/nullptr/hidemyapplist/xposed/hook/PmsHookTargetBase.kt @@ -25,16 +25,40 @@ abstract class PmsHookTargetBase(protected val service: HMAService) : IFramework 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 + try { + Utils.getPackageInfoCompat( + service.pms, + VENDING_PACKAGE_NAME, + PackageManager.GET_SIGNING_CERTIFICATES.toLong(), + 0 + ).signingInfo + } catch (_: Throwable) { + null + } } - abstract val fakeSystemPackageInstallInfo: Any? - abstract val fakeUserPackageInstallInfo: Any? + /* + protected val psSigningDetails by lazy { + // TODO: Implement vending signing details + null + } + + // TODO: Spoof PM install source + internal val fakeSystemPackageInstallSource: Any? by lazy { + try { + findField("com.android.server.pm.InstallSource") { + name == "EMPTY_ORPHANED" + }.get(null)!! + } catch (_: Throwable) { + null + } + } + + abstract val fakeUserPackageInstallSource: Any? + */ + + abstract val fakeSystemPackageInstallSourceInfo: Any? + abstract val fakeUserPackageInstallSourceInfo: Any? override fun load() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { @@ -74,7 +98,55 @@ abstract class PmsHookTargetBase(protected val service: HMAService) : IFramework } } + if (service.pmn != null) { + findMethodOrNull(service.pmn::class.java, findSuper = true) { + name == "getInstallerForPackage" + }?.hookBefore { param -> + val query = param.args[0] as String? + val callingHandle = Binder.getCallingUserHandle() + + val callingApps = Utils.binderLocalScope { + service.pms.getPackagesForUid(callingHandle.hashCode()) + } ?: return@hookBefore + + for (caller in callingApps) { + when (service.shouldHideInstallationSource(caller, query, callingHandle)) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = "preload" + else -> continue + } + } + }?.let { + logD(TAG, "PMN getInstallerForPackage is hooked!") + hooks.add(it) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + /* + findMethodOrNull(service.pms::class.java, findSuper = true) { + name == "getInstallSource" + }?.hookBefore { param -> + val query = param.args[0] as String? + val callingUid = param.args[1] as Int + val user = UserHandle.getUserHandleForUid(param.args[2] as Int) + + val callingApps = Utils.binderLocalScope { + service.pms.getPackagesForUid(callingUid) + } ?: return@hookBefore + + for (caller in callingApps) { + when (service.shouldHideInstallationSource(caller, query, user)) { + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = fakeUserPackageInstallSource + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = fakeSystemPackageInstallSource + else -> continue + } + } + }?.let { + hooks.add(it) + } + */ + findMethodOrNull(service.pms::class.java, findSuper = true) { name == "getInstallSourceInfo" }?.hookBefore { param -> @@ -83,13 +155,15 @@ abstract class PmsHookTargetBase(protected val service: HMAService) : IFramework 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 + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = fakeUserPackageInstallSourceInfo + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = fakeSystemPackageInstallSourceInfo else -> continue } @@ -114,8 +188,8 @@ abstract class PmsHookTargetBase(protected val service: HMAService) : IFramework } ?: return@hookBefore for (caller in callingApps) { when (service.shouldHideInstallationSource(caller, query, user)) { - 1 -> param.result = VENDING_PACKAGE_NAME - 2 -> param.result = null + Constants.FAKE_INSTALLATION_SOURCE_USER -> param.result = VENDING_PACKAGE_NAME + Constants.FAKE_INSTALLATION_SOURCE_SYSTEM -> param.result = null else -> continue }