- Fix AppSettingsFragment crash for uninstalled apps - Remove package from search query if uninstalled - Refresh + sort list when the package status changed - Add "Clear uninstalled app configs" option - Remove LSPlant dependent apps from suspicious apps list - Fix some of detections by reverting dev options changes, but do not remove them - Optimize imports
This commit is contained in:
parent
f435563318
commit
5c4205303e
14 changed files with 170 additions and 40 deletions
9
.github/workflows/pull_request.yml
vendored
9
.github/workflows/pull_request.yml
vendored
|
|
@ -59,5 +59,12 @@ jobs:
|
|||
- name: Build debug
|
||||
id: buildDebug
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew assembleDebug
|
||||
echo "debugName=$(ls app/build/outputs/apk/debug/*.apk | awk -F '(/|.apk)' '{print $6}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload debug
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.buildDebug.outputs.debugName }}
|
||||
path: "app/build/outputs/apk/debug/*.apk"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.util.Log
|
|||
import icu.nullptr.hidemyapplist.common.JsonConfig
|
||||
import icu.nullptr.hidemyapplist.hmaApp
|
||||
import icu.nullptr.hidemyapplist.ui.util.showToast
|
||||
import icu.nullptr.hidemyapplist.util.PackageHelper
|
||||
import org.frknkrc44.hma_oss.R
|
||||
import org.frknkrc44.hma_oss.common.BuildConfig
|
||||
import java.io.File
|
||||
|
|
@ -179,4 +180,28 @@ object ConfigManager {
|
|||
else config.scope[packageName] = appConfig
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
fun clearUninstalledAppConfigs(onFinish: (success: Boolean) -> Unit) {
|
||||
PackageHelper.invalidateCache { throwable ->
|
||||
if (throwable == null) {
|
||||
val markedToRemove = mutableListOf<String>()
|
||||
config.scope.keys.forEach { packageName ->
|
||||
if (!PackageHelper.exists(packageName)) {
|
||||
markedToRemove.add(packageName)
|
||||
}
|
||||
}
|
||||
|
||||
if (markedToRemove.isNotEmpty()) {
|
||||
markedToRemove.forEach { config.scope.remove(it) }
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
ServiceClient.log(Log.INFO, TAG, "Pruned ${markedToRemove.size} app config(s)")
|
||||
|
||||
onFinish(true)
|
||||
} else {
|
||||
onFinish(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package icu.nullptr.hidemyapplist.ui.fragment
|
|||
import icu.nullptr.hidemyapplist.service.ConfigManager
|
||||
import icu.nullptr.hidemyapplist.ui.adapter.AppManageAdapter
|
||||
import icu.nullptr.hidemyapplist.ui.util.navigate
|
||||
import icu.nullptr.hidemyapplist.util.PackageHelper
|
||||
import org.frknkrc44.hma_oss.R
|
||||
|
||||
class AppManageFragment : AppSelectFragment() {
|
||||
|
|
@ -10,7 +11,9 @@ class AppManageFragment : AppSelectFragment() {
|
|||
override val firstComparator: Comparator<String> = Comparator.comparing(ConfigManager::isHideEnabled).reversed()
|
||||
|
||||
override val adapter = AppManageAdapter {
|
||||
val args = AppSettingsFragmentArgs(it)
|
||||
navigate(R.id.nav_app_settings, args.toBundle())
|
||||
if (PackageHelper.exists(it)) {
|
||||
val args = AppSettingsFragmentArgs(it)
|
||||
navigate(R.id.nav_app_settings, args.toBundle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
|
|||
}
|
||||
}
|
||||
|
||||
open fun invalidateCache() {
|
||||
protected open fun invalidateCache() {
|
||||
PackageHelper.invalidateCache()
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +81,7 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
|
|||
PrefManager.appFilter_reverseOrder = item.isChecked
|
||||
}
|
||||
}
|
||||
|
||||
sortList()
|
||||
}
|
||||
|
||||
|
|
@ -144,8 +145,12 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
|
|||
lifecycleScope.launch {
|
||||
PackageHelper.isRefreshing
|
||||
.flowWithLifecycle(lifecycle)
|
||||
.collect {
|
||||
binding.swipeRefresh.isRefreshing = it
|
||||
.collect { isRefreshing ->
|
||||
binding.swipeRefresh.isRefreshing = isRefreshing
|
||||
|
||||
if (!isRefreshing) {
|
||||
sortList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.view.WindowInsets
|
|||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceDataStore
|
||||
|
|
@ -17,8 +18,8 @@ import androidx.preference.SwitchPreferenceCompat
|
|||
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import icu.nullptr.hidemyapplist.common.PropertyUtils
|
||||
import icu.nullptr.hidemyapplist.common.Constants
|
||||
import icu.nullptr.hidemyapplist.common.PropertyUtils
|
||||
import icu.nullptr.hidemyapplist.hmaApp
|
||||
import icu.nullptr.hidemyapplist.service.ConfigManager
|
||||
import icu.nullptr.hidemyapplist.service.PrefManager
|
||||
|
|
@ -30,6 +31,7 @@ import icu.nullptr.hidemyapplist.ui.util.showToast
|
|||
import icu.nullptr.hidemyapplist.util.ConfigUtils.Companion.getLocale
|
||||
import icu.nullptr.hidemyapplist.util.LangList
|
||||
import icu.nullptr.hidemyapplist.util.SuUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import org.frknkrc44.hma_oss.R
|
||||
import org.frknkrc44.hma_oss.databinding.FragmentSettingsBinding
|
||||
import java.util.Locale
|
||||
|
|
@ -287,6 +289,40 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
|
|||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("clearUninstalledPackageConfigs")?.apply {
|
||||
setOnPreferenceClickListener {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.settings_clear_uninstalled_app_configs)
|
||||
.setMessage(R.string.settings_no_undone_warning)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val progressDialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.settings_clear_uninstalled_app_configs)
|
||||
.setView(R.layout.dialog_loading)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
|
||||
progressDialog.show()
|
||||
|
||||
ConfigManager.clearUninstalledAppConfigs { isSuccess ->
|
||||
lifecycleScope.launch {
|
||||
progressDialog.dismiss()
|
||||
|
||||
if (isSuccess) {
|
||||
showToast(android.R.string.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
// do nothing
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<SwitchPreferenceCompat>("blackDarkTheme")?.apply {
|
||||
isEnabled = findPreference<SwitchPreferenceCompat>("systemWallpaper")?.isChecked != true
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
package icu.nullptr.hidemyapplist.util
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.IPackageManager
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import icu.nullptr.hidemyapplist.common.Constants
|
||||
import icu.nullptr.hidemyapplist.hmaApp
|
||||
import icu.nullptr.hidemyapplist.service.PrefManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
|
@ -19,7 +17,6 @@ import java.text.Collator
|
|||
import java.util.Locale
|
||||
|
||||
object PackageHelper {
|
||||
|
||||
class PackageCache(
|
||||
val info: PackageInfo,
|
||||
val label: String,
|
||||
|
|
@ -61,26 +58,22 @@ object PackageHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private lateinit var ipm: IPackageManager
|
||||
private lateinit var pm: PackageManager
|
||||
|
||||
private val packageCache = MutableSharedFlow<Map<String, PackageCache>>(replay = 1)
|
||||
private val mAppList = MutableSharedFlow<List<String>>(replay = 1)
|
||||
val appList: SharedFlow<List<String>> = mAppList
|
||||
val appList = MutableSharedFlow<List<String>>(replay = 1)
|
||||
|
||||
private val mRefreshing = MutableSharedFlow<Boolean>(replay = 1)
|
||||
val isRefreshing: SharedFlow<Boolean> = mRefreshing
|
||||
val isRefreshing = MutableSharedFlow<Boolean>(replay = 1)
|
||||
|
||||
init {
|
||||
// TODO: PackageManagerDelegate
|
||||
pm = hmaApp.packageManager
|
||||
invalidateCache()
|
||||
}
|
||||
|
||||
fun invalidateCache() {
|
||||
fun invalidateCache(
|
||||
onFinished: ((Throwable?) -> Unit)? = null
|
||||
) {
|
||||
hmaApp.globalScope.launch {
|
||||
mRefreshing.emit(true)
|
||||
isRefreshing.emit(true)
|
||||
val cache = withContext(Dispatchers.IO) {
|
||||
val pm = hmaApp.packageManager
|
||||
val packages = pm.getInstalledPackages(0)
|
||||
mutableMapOf<String, PackageCache>().also {
|
||||
for (packageInfo in packages) {
|
||||
|
|
@ -94,8 +87,12 @@ object PackageHelper {
|
|||
}
|
||||
}
|
||||
packageCache.emit(cache)
|
||||
mAppList.emit(cache.keys.toList())
|
||||
mRefreshing.emit(false)
|
||||
appList.emit(cache.keys.toList())
|
||||
isRefreshing.emit(false)
|
||||
}.apply {
|
||||
if (onFinished != null) {
|
||||
invokeOnCompletion(onFinished)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,27 +104,37 @@ object PackageHelper {
|
|||
PrefManager.SortMethod.BY_UPDATE_TIME -> Comparators.byUpdateTime
|
||||
}
|
||||
if (PrefManager.appFilter_reverseOrder) comparator = comparator.reversed()
|
||||
val list = mAppList.first().sortedWith(firstComparator.then(comparator))
|
||||
mAppList.emit(list)
|
||||
val list = appList.first().sortedWith(firstComparator.then(comparator))
|
||||
appList.emit(list)
|
||||
}
|
||||
|
||||
private suspend fun getCacheNoThrow() = try {
|
||||
packageCache.first()
|
||||
} catch (_: Throwable) {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
fun exists(packageName: String) = runBlocking {
|
||||
packageCache.first().contains(packageName)
|
||||
getCacheNoThrow().contains(packageName)
|
||||
}
|
||||
|
||||
fun loadPackageInfo(packageName: String): PackageInfo = runBlocking {
|
||||
packageCache.first()[packageName]!!.info
|
||||
getCacheNoThrow()[packageName]!!.info
|
||||
}
|
||||
|
||||
fun loadAppLabel(packageName: String): String = runBlocking {
|
||||
packageCache.first()[packageName]!!.label
|
||||
getCacheNoThrow()[packageName]?.label ?: packageName
|
||||
}
|
||||
|
||||
fun loadAppIcon(packageName: String): Bitmap = runBlocking {
|
||||
packageCache.first()[packageName]!!.icon
|
||||
getCacheNoThrow()[packageName]?.icon ?:
|
||||
BitmapFactory.decodeResource(
|
||||
hmaApp.resources,
|
||||
android.R.drawable.sym_def_app_icon
|
||||
)
|
||||
}
|
||||
|
||||
fun isSystem(packageName: String): Boolean = runBlocking {
|
||||
packageCache.first()[packageName]?.info?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
getCacheNoThrow()[packageName]?.info?.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) != 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
app/src/main/res/layout/dialog_loading.xml
Normal file
14
app/src/main/res/layout/dialog_loading.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:padding="@dimen/item_padding_vertical"
|
||||
android:gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:indeterminate="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -158,6 +158,9 @@
|
|||
<string name="settings_disable_activity_launch_protection">Disable Activity launch protection</string>
|
||||
<string name="settings_disable_activity_launch_protection_summary">This toggle will lift all startActivity protections. If you\'re unsure, don\'t enable it.</string>
|
||||
<string name="settings_system_wallpaper">Use system wallpaper</string>
|
||||
<string name="settings_clear_uninstalled_app_configs">Clear uninstalled app configs</string>
|
||||
<string name="settings_clear_uninstalled_app_configs_summary">This option allows you to create a config without remnants but you\'ll have to reconfigure apps when you installed them again.</string>
|
||||
<string name="settings_no_undone_warning">This action cannot be undone.</string>
|
||||
|
||||
<string name="settings_language">Language</string>
|
||||
<string name="settings_translate">Participate in translation</string>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@
|
|||
android:key="hideIcon"
|
||||
android:summary="@string/settings_hide_icon_summary"
|
||||
android:title="@string/settings_hide_icon" />
|
||||
<Preference
|
||||
android:icon="@drawable/outline_delete_24"
|
||||
android:key="clearUninstalledPackageConfigs"
|
||||
android:summary="@string/settings_clear_uninstalled_app_configs_summary"
|
||||
android:title="@string/settings_clear_uninstalled_app_configs" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_service">
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ fun String.execute(currentWorkingDir: File = file("./")): String {
|
|||
|
||||
val localProperties = Properties()
|
||||
localProperties.load(file("local.properties").inputStream())
|
||||
val ciBuild = providers.environmentVariable("CI").isPresent
|
||||
val officialBuild by extra(localProperties.getProperty("officialBuild", "false") == "true")
|
||||
|
||||
@Suppress("unused")
|
||||
|
|
@ -30,10 +31,15 @@ val crowdinApiKey: String by extra(localProperties.getProperty("crowdinApiKey",
|
|||
fun getUncommittedSuffix(): String {
|
||||
if (officialBuild) return ""
|
||||
|
||||
if (ciBuild) {
|
||||
val headRefVal = providers.environmentVariable("GITHUB_HEAD_REF").orElse("HEAD").get()
|
||||
return "-$headRefVal"
|
||||
}
|
||||
|
||||
var returnedVal = ""
|
||||
|
||||
try {
|
||||
val branch = "git symbolic-ref HEAD".execute().split("/").last()
|
||||
val branch = "git rev-parse --abbrev-ref HEAD".execute().split("/").last()
|
||||
if (branch != "master") {
|
||||
returnedVal += "-$branch"
|
||||
}
|
||||
|
|
@ -48,7 +54,7 @@ fun getUncommittedSuffix(): String {
|
|||
}
|
||||
|
||||
val gitHasUncommittedSuffix = getUncommittedSuffix()
|
||||
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
||||
val gitCommitCount = "git rev-list refs/remotes/origin/master --count".execute().toInt()
|
||||
|
||||
// 432 is the count of commits before license changed
|
||||
val gitCommitCountAfterOss = gitCommitCount - 432
|
||||
|
|
|
|||
|
|
@ -29,9 +29,16 @@ class SuspiciousAppsPreset : BasePreset("sus_apps") {
|
|||
"com.amaze.filemanager",
|
||||
)
|
||||
|
||||
val libNames = arrayOf(
|
||||
"liblsplant.so",
|
||||
/*
|
||||
val libNames = arrayOf<String>(
|
||||
// Note that LSPlant is used in some of games too
|
||||
// I will not add it as suspicious app indicator anymore
|
||||
//
|
||||
// "liblsplant.so",
|
||||
|
||||
// TODO: Add more suspicious apps by checking for libs
|
||||
)
|
||||
*/
|
||||
|
||||
val assetNames = arrayOf(
|
||||
// ~All possible APK editors
|
||||
|
|
@ -101,7 +108,7 @@ class SuspiciousAppsPreset : BasePreset("sus_apps") {
|
|||
}
|
||||
|
||||
ZipFile(appInfo.sourceDir).use { zipFile ->
|
||||
if (findAppsFromLibs(zipFile, libNames) || findAppsFromAssets(zipFile, assetNames)) {
|
||||
if (/*findAppsFromLibs(zipFile, libNames) ||*/ findAppsFromAssets(zipFile, assetNames)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
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<ReplacementItem>
|
||||
|
||||
fun getSpoofedValue(key: String) = settingsKVPairs.firstOrNull { it.name == key }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class DeveloperOptionsPreset : BasePreset("dev_options") {
|
|||
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
|
||||
"0",
|
||||
),
|
||||
/*
|
||||
ReplacementItem(
|
||||
"debug.force_rtl",
|
||||
"0"
|
||||
|
|
@ -100,5 +101,6 @@ class DeveloperOptionsPreset : BasePreset("dev_options") {
|
|||
"non_persistent_mac_randomization_force_enabled",
|
||||
"0",
|
||||
),
|
||||
*/
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package icu.nullptr.hidemyapplist.common.settings_presets
|
|||
|
||||
data class ReplacementItem(
|
||||
val name: String,
|
||||
val value: String?
|
||||
val value: String?,
|
||||
// val database: String = BasePreset.SETTINGS_GLOBAL,
|
||||
) {
|
||||
override fun toString() = "ReplacementItem {" +
|
||||
" 'name': '$name'," +
|
||||
"'value': '$value'" +
|
||||
override fun toString() = "ReplacementItem { " +
|
||||
"'name': '$name', " +
|
||||
"'value': '$value', " +
|
||||
// "'database': '$database'" +
|
||||
" }"
|
||||
}
|
||||
Loading…
Reference in a new issue