diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index bbaa8f13..1fec2dc2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -11,7 +11,7 @@ val props =
val keyStoreFile = file(signFolder + props.getProperty("storeFile"))
android {
namespace = "com.aistra.hail"
- compileSdk = 33
+ compileSdk = 34
defaultConfig {
applicationId = "com.aistra.hail"
@@ -66,7 +66,6 @@ android {
}
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.biometric:biometric:1.1.0")
@@ -76,13 +75,14 @@ dependencies {
implementation("androidx.preference:preference-ktx:1.2.0")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.work:work-runtime-ktx:2.8.1")
+ implementation("com.belerweb:pinyin4j:2.5.1")
implementation("com.google.android.material:material:1.9.0")
implementation("dev.rikka.rikkax.preference:simplemenu-preference:1.0.3")
implementation("dev.rikka.shizuku:api:13.1.4")
implementation("dev.rikka.shizuku:provider:13.1.4")
implementation("io.github.iamr0s:Dhizuku-API:2.4")
implementation("me.zhanghai.android.appiconloader:appiconloader:1.5.0")
- implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
- implementation("com.belerweb:pinyin4j:2.5.1")
implementation("org.apache.commons:commons-text:1.10.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
+ implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5c3394c9..f1417dfc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
+
+
HShizuku.setAppDisabled(packageName, frozen)
HailData.MODE_SHIZUKU_HIDE -> HShizuku.setAppHidden(packageName, frozen)
HailData.MODE_SHIZUKU_SUSPEND -> HShizuku.setAppSuspended(packageName, frozen)
+ HailData.MODE_ISLAND_HIDE -> HIsland.setAppHidden(packageName, frozen)
+ HailData.MODE_ISLAND_SUSPEND -> HIsland.setAppSuspended(packageName, frozen)
else -> false
}
diff --git a/app/src/main/kotlin/com/aistra/hail/app/HailData.kt b/app/src/main/kotlin/com/aistra/hail/app/HailData.kt
index 15724dd2..a70c4a21 100644
--- a/app/src/main/kotlin/com/aistra/hail/app/HailData.kt
+++ b/app/src/main/kotlin/com/aistra/hail/app/HailData.kt
@@ -32,6 +32,7 @@ object HailData {
const val DHIZUKU = "dhizuku_"
const val SU = "su_"
const val SHIZUKU = "shizuku_"
+ const val ISLAND = "island_"
const val DISABLE = "disable"
const val HIDE = "hide"
const val SUSPEND = "suspend"
@@ -45,6 +46,8 @@ object HailData {
const val MODE_SHIZUKU_DISABLE = SHIZUKU + DISABLE
const val MODE_SHIZUKU_HIDE = SHIZUKU + HIDE
const val MODE_SHIZUKU_SUSPEND = SHIZUKU + SUSPEND
+ const val MODE_ISLAND_HIDE = ISLAND + HIDE
+ const val MODE_ISLAND_SUSPEND = ISLAND + SUSPEND
private const val TILE_ACTION = "tile_action"
const val DYNAMIC_SHORTCUT_ACTION = "dynamic_shortcut_action"
const val ACTION_NONE = "none"
diff --git a/app/src/main/kotlin/com/aistra/hail/extensions/ViewExtensions.kt b/app/src/main/kotlin/com/aistra/hail/extensions/ViewExtensions.kt
index 25e415ff..d491c6a4 100644
--- a/app/src/main/kotlin/com/aistra/hail/extensions/ViewExtensions.kt
+++ b/app/src/main/kotlin/com/aistra/hail/extensions/ViewExtensions.kt
@@ -9,7 +9,7 @@ val View.isRtl get() = layoutDirection == View.LAYOUT_DIRECTION_RTL
/**
* Very easy to apply insets to a view.
- * */
+ */
fun View.applyInsetsPadding(
start: Boolean = false,
end: Boolean = false,
diff --git a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt
index a978b039..2da1c3f6 100644
--- a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt
+++ b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt
@@ -26,6 +26,8 @@ import com.aistra.hail.app.HailApi.addTag
import com.aistra.hail.app.HailData
import com.aistra.hail.databinding.DialogInputBinding
import com.aistra.hail.databinding.FragmentPagerBinding
+import com.aistra.hail.extensions.applyInsetsPadding
+import com.aistra.hail.extensions.isLandscape
import com.aistra.hail.ui.main.MainFragment
import com.aistra.hail.utils.FuzzySearch
import com.aistra.hail.utils.HPackages
@@ -89,6 +91,12 @@ class PagerFragment : MainFragment(), PagerAdapter.OnItemClickListener,
}
}
})
+
+ this.applyInsetsPadding(
+ start = !activity.isLandscape,
+ end = true,
+ bottom = activity.isLandscape
+ )
}
binding.refresh.setOnRefreshListener {
updateCurrentList()
diff --git a/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt
index 348a1459..a5f93043 100644
--- a/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt
+++ b/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt
@@ -6,6 +6,7 @@ import android.os.Bundle
import android.provider.Settings
import android.view.*
import android.widget.FrameLayout
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.NotificationManagerCompat
import androidx.core.view.MenuHost
@@ -42,6 +43,7 @@ import rikka.shizuku.Shizuku
class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener,
MenuProvider {
+ private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@@ -292,6 +294,27 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan
HUI.showToast(R.string.shizuku_missing)
false
}
+
+ mode.startsWith(HailData.ISLAND) -> return runCatching{
+ when{
+ mode == HailData.MODE_ISLAND_HIDE && HIsland.freezePermissionGranted() -> true
+ mode == HailData.MODE_ISLAND_SUSPEND && HIsland.suspendPermissionGranted() -> true
+ else -> {
+ lifecycleScope.launch {
+ requestPermissionLauncher.launch(if(mode == HailData.MODE_ISLAND_HIDE)
+ HIsland.PERMISSION_FREEZE_PACKAGE
+ else
+ HIsland.PERMISSION_SUSPEND_PACKAGE
+ )
+ }
+ false
+ }
+ }
+ }.getOrElse {
+ HLog.e(it)
+ HUI.showToast(R.string.permission_denied)
+ false
+ }
}
return true
diff --git a/app/src/main/kotlin/com/aistra/hail/utils/FuzzySearch.kt b/app/src/main/kotlin/com/aistra/hail/utils/FuzzySearch.kt
index 208f064d..b69dab44 100644
--- a/app/src/main/kotlin/com/aistra/hail/utils/FuzzySearch.kt
+++ b/app/src/main/kotlin/com/aistra/hail/utils/FuzzySearch.kt
@@ -2,64 +2,36 @@ package com.aistra.hail.utils
import org.apache.commons.text.similarity.LevenshteinDistance
-/**使用莱文斯坦距离 (Levenshtein distance)实现模糊搜索*/
+/** 使用莱文斯坦距离 (Levenshtein distance) 实现模糊搜索 */
object FuzzySearch {
- private val levenshteinDistance: LevenshteinDistance = LevenshteinDistance()
+ private val levenshteinDistance = LevenshteinDistance()
/**
- * 两个字符串差异小于搜索字符串长度 且 搜索字符全部包含在搜索字符串中 则显示在搜索结果中
- * @param textToSearch 尝试匹配的字符串
- * @param query 用户输入字符串
- * */
- fun search(textToSearch: String?, query: String?): Boolean {
- if (query.isNullOrEmpty()) {
- return true
- }
- if (textToSearch.isNullOrEmpty()) {
- return false
- }
- if (textToSearch.contains(query, true)) {
- return true
- }
- val textToSearchUpp = textToSearch.uppercase()
+ * 两个字符串差异小于原始字符串长度 且 原始字符串依次包含输入字符串的每个字符 则显示在搜索结果中
+ * @param raw 需要匹配的原始字符串
+ * @param query 输入的字符串
+ */
+ fun search(raw: String?, query: String?): Boolean {
+ if (query.isNullOrEmpty()) return true
+ if (raw.isNullOrEmpty()) return false
+ if (raw.contains(query, true)) return true
+ val rawUpp = raw.uppercase()
val queryUpp = query.uppercase()
- val diff = levenshteinDistance.apply(textToSearchUpp, queryUpp)
- val lenTextToSearch = textToSearchUpp.length
- return diff < lenTextToSearch && containsInOrder(textToSearchUpp, queryUpp)
+ val diff = levenshteinDistance.apply(rawUpp, queryUpp)
+ return diff < rawUpp.length && containsInOrder(rawUpp, queryUpp)
}
- /**
- * 判断一个字符串A是否依次包含另一个字符串B的每个字符,并且这些字符是按顺序从A的开头开始的
- * @param strA
- * @param strB
- * */
+ /** 判断字符串A是否依次包含字符串B的每个字符 */
private fun containsInOrder(strA: String, strB: String): Boolean {
var indexA = 0 // 用于跟踪字符串A中的位置
for (charB in strB) {
// 在字符串A的当前位置之后查找字符charB
val foundIndex = strA.indexOf(charB, indexA)
// 如果未找到字符或者字符的位置不是当前位置,表示不包含按顺序的字符
- if (foundIndex == -1) {
- return false
- }
+ if (foundIndex == -1) return false
// 移动到下一个位置,以便查找下一个字符
indexA = foundIndex + 1
}
return true
}
-
- private fun assertTrue(res: Boolean) {
- if (!res){
- throw RuntimeException("测试失败!")
- }
- }
-
- @JvmStatic
- fun main(args: Array) {
- assertTrue(search("支付宝", "支"))
- assertTrue(search("World Peace", "wp"))
- assertTrue(search("World Peace", "pee"))
- assertTrue(!search("World Peace", "dow"))
- assertTrue(containsInOrder("小费计算器", "小器"))
- }
}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/aistra/hail/utils/HIsland.kt b/app/src/main/kotlin/com/aistra/hail/utils/HIsland.kt
new file mode 100644
index 00000000..9eabea3d
--- /dev/null
+++ b/app/src/main/kotlin/com/aistra/hail/utils/HIsland.kt
@@ -0,0 +1,77 @@
+package com.aistra.hail.utils
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.core.content.ContextCompat
+import com.aistra.hail.HailApp.Companion.app
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+
+object HIsland {
+ const val PERMISSION_FREEZE_PACKAGE = "com.oasisfeng.island.permission.FREEZE_PACKAGE"
+ const val PERMISSION_SUSPEND_PACKAGE = "com.oasisfeng.island.permission.SUSPEND_PACKAGE"
+ private const val ACTION_SUSPEND = "com.oasisfeng.island.action.SUSPEND"
+ private const val ACTION_UNSUSPEND = "com.oasisfeng.island.action.UNSUSPEND"
+ private const val ACTION_FREEZE = "com.oasisfeng.island.action.FREEZE"
+ private const val ACTION_UNFREEZE = "com.oasisfeng.island.action.UNFREEZE"
+ private const val EXTRA_CALLER_ID = "caller"
+
+ private val thread by lazy { HandlerThread("HIsland").apply { start() } }
+ private val handler by lazy { Handler(thread.looper) }
+
+ fun freezePermissionGranted(): Boolean {
+ return ContextCompat.checkSelfPermission(
+ app, PERMISSION_FREEZE_PACKAGE
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
+ fun suspendPermissionGranted(): Boolean {
+ return ContextCompat.checkSelfPermission(
+ app, PERMISSION_SUSPEND_PACKAGE
+ ) == PackageManager.PERMISSION_GRANTED
+ }
+
+ private fun setAppFrozen(packageName: String, action: String): Boolean {
+ val intent = Intent(action).apply {
+ data = Uri.fromParts("package", packageName, null)
+ setPackage("com.oasisfeng.island")
+ addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ putExtra(
+ EXTRA_CALLER_ID,
+ PendingIntent.getActivity(app, 0, Intent(), PendingIntent.FLAG_IMMUTABLE)
+ )
+ }
+ val result = CompletableDeferred()
+ app.sendOrderedBroadcast(
+ intent, null, object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (resultCode != Activity.RESULT_OK) {
+ HLog.i("HIsland", resultData)
+ }
+ result.complete(resultCode == Activity.RESULT_OK)
+ }
+ }, handler, Activity.RESULT_OK, null, null
+ )
+ return runBlocking {
+ runCatching {
+ withTimeout(500L) {
+ result.await()
+ }
+ }.getOrElse { false }
+ }
+ }
+
+ fun setAppHidden(packageName: String, hidden: Boolean): Boolean =
+ HTarget.N && setAppFrozen(packageName, if (hidden) ACTION_FREEZE else ACTION_UNFREEZE)
+
+ fun setAppSuspended(packageName: String, suspended: Boolean): Boolean =
+ HTarget.N && setAppFrozen(packageName, if (suspended) ACTION_SUSPEND else ACTION_UNSUSPEND)
+}
\ No newline at end of file
diff --git a/app/src/main/kotlin/com/aistra/hail/utils/PinyinSearch.kt b/app/src/main/kotlin/com/aistra/hail/utils/PinyinSearch.kt
index 8b11fcaf..6ebbcecf 100644
--- a/app/src/main/kotlin/com/aistra/hail/utils/PinyinSearch.kt
+++ b/app/src/main/kotlin/com/aistra/hail/utils/PinyinSearch.kt
@@ -3,77 +3,61 @@ package com.aistra.hail.utils
import net.sourceforge.pinyin4j.PinyinHelper
import java.util.Locale
-
-/**中文拼音搜索类*/
+/** 中文拼音搜索类 */
object PinyinSearch {
/**
- * 分别使用首字母和全拼进行匹配,满足条件之一就返回true,
- * 如果当前语言不是中文就直接返回false
- * @param textToSearch 需要匹配的字符串
- * @param query 用户输入
- * */
- fun searchPinyinAll(textToSearch: String?, query: String?): Boolean {
- if (query.isNullOrEmpty()) {
- return true
- }
- if (textToSearch.isNullOrEmpty()) {
- return false
- }
- val language = Locale.getDefault().language
- return if (language.equals(Locale.CHINESE.language)) {
- searchCap(textToSearch, query) || searchAllSpell(textToSearch, query)
- } else {
- false
- }
+ * 当前语言是中文时使用首字母和全拼进行搜索,满足任一条件则显示在搜索结果中
+ * @param raw 需要匹配的原始字符串
+ * @param query 输入的字符串
+ */
+ fun searchPinyinAll(raw: String?, query: String?): Boolean {
+ if (query.isNullOrEmpty()) return true
+ if (raw.isNullOrEmpty()) return false
+ return if (Locale.getDefault().language != Locale.CHINESE.language) false
+ else searchCap(raw, query) || searchAllSpell(raw, query)
}
/**
- * 根据首字母进行搜索
- * 比如搜索”计算器“ 只需要输入 ”jsq“
- * */
- fun searchCap(appName: String, pinyinCap: String): Boolean {
- if (pinyinCap.length < 80) {
- for (index in getNameStringList(appName)) {
- if (index.contains(pinyinCap, true)) {
- return true
- }
+ * 根据拼音首字母进行搜索
+ * 如搜索“计算器”时只需输入 "jsq"
+ */
+ private fun searchCap(raw: String, pinyinCap: String): Boolean {
+ if (pinyinCap.length > 8) return false // "最强多媒体播放器".length
+ for (index in getNameStringList(raw)) {
+ if (index.contains(pinyinCap, true)) {
+ return true
}
- return false
- } else {
- return false
}
+ return false
}
/**
* 根据全部拼音进行搜索
- * 比如搜索 计算器 只需要输入 "jisuanqi"
- * */
- fun searchAllSpell(appName: String, pinyinAll: String): Boolean {
- if (pinyinAll.length < 30) {
- for (index in getNameStringPinyinAll(appName)) {
- if (index.contains(pinyinAll, true)) {
- return true
- }
+ * 如搜索“计算器”时只需输入 "jisuanqi"
+ */
+ private fun searchAllSpell(raw: String, pinyinAll: String): Boolean {
+ if (pinyinAll.length > 48) return false // "chuang".length * 8
+ for (index in getNameStringPinyinAll(raw)) {
+ if (index.contains(pinyinAll, true)) {
+ return true
}
- return false
- } else {
- return false
}
+ return false
}
- fun getNameStringPinyinAll(target: String): ArrayList {
+ private fun getNameStringPinyinAll(target: String): ArrayList {
val res = ArrayList()
getNameCapListPinyinAll(Array(target.length) { "" }, 0, target, res)
return res
}
- fun getNameStringList(target: String): ArrayList {
+ private fun getNameStringList(target: String): ArrayList {
val res = ArrayList()
getNameCapList(CharArray(target.length), 0, target, res)
return res
}
- fun getNameCapList(
+ private fun getNameCapList(
capList: CharArray, currentIndex: Int, target: String, result: ArrayList
) {
if (currentIndex == target.length - 1) {
@@ -109,7 +93,7 @@ object PinyinSearch {
}
}
- fun getNameCapListPinyinAll(
+ private fun getNameCapListPinyinAll(
fullList: Array, currentIndex: Int, target: String, result: ArrayList
) {
if (currentIndex == target.length - 1) {
diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml
index 682cc413..c730518b 100644
--- a/app/src/main/res/layout/fragment_about.xml
+++ b/app/src/main/res/layout/fragment_about.xml
@@ -32,8 +32,10 @@
+ app:contentPadding="-18dp"
+ app:srcCompat="@drawable/ic_launcher_foreground" />
Superusuario - Ocultar
F-Droid
Omitir
+ Isla - Ocultar
+ Isla - Suspender
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index dbfd4d40..c8f7a5fd 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -138,6 +138,8 @@
终端
Dhizuku - 隐藏
Dhizuku - 暂停
+ 炼妖壶 - 隐藏
+ 炼妖壶 - 暂停
超级用户 - 隐藏
F-Droid
跳过
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 703018d9..2a6b19f8 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -41,6 +41,8 @@
- @string/mode_dhizuku_suspend
- @string/mode_owner_hide
- @string/mode_owner_suspend
+ - @string/mode_island_hide
+ - @string/mode_island_suspend
- default
@@ -54,6 +56,8 @@
- dhizuku_suspend
- owner_hide
- owner_suspend
+ - island_hide
+ - island_suspend
- @string/donate_alipay
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f609918f..af9fef0f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -140,4 +140,6 @@
Superuser - Hide
F-Droid
Skip
+ Island - Hide
+ Island - Suspend
\ No newline at end of file