Skip to content

Commit dd59024

Browse files
committed
Update
1 parent 86d31bd commit dd59024

1 file changed

Lines changed: 165 additions & 123 deletions

File tree

Lines changed: 165 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.tool.tree
22

33
import android.Manifest
4+
import android.content.Context
45
import android.content.Intent
56
import android.content.pm.PackageManager
7+
import android.content.res.Configuration
68
import android.net.Uri
79
import android.os.Build
810
import android.os.Bundle
@@ -20,180 +22,179 @@ import com.omarea.common.shell.ShellExecutor
2022
import com.omarea.common.ui.DialogHelper
2123
import com.omarea.krscript.executor.ScriptEnvironmen
2224
import com.tool.tree.databinding.ActivitySplashBinding
23-
import kotlinx.coroutines.*
25+
import kotlinx.coroutines.Dispatchers
26+
import kotlinx.coroutines.launch
27+
import kotlinx.coroutines.withContext
2428
import java.io.BufferedReader
2529
import java.io.DataOutputStream
2630
import java.io.File
27-
import android.content.res.Configuration
2831
import java.util.Locale
2932

3033
class SplashActivity : AppCompatActivity() {
3134

3235
private lateinit var binding: ActivitySplashBinding
3336
private val REQUEST_CODE_PERMISSIONS = 1001
34-
private val REQUEST_CODE_MANAGE_STORAGE = 1002
37+
private val REQUEST_CODE_MANAGE_ALL_FILES = 1002
3538

3639
private var hasRoot = false
37-
private var startingJob: Job? = null // Dùng Job để quản lý luồng khởi động
38-
39-
private val rows = mutableListOf<String>()
40-
private var ignored = false
41-
private val maxLines = 5
40+
private var started = false
41+
private var starting = false
4242

4343
override fun onCreate(savedInstanceState: Bundle?) {
4444
applyAppLanguage()
4545
super.onCreate(savedInstanceState)
46-
4746
ThemeModeState.switchTheme(this)
4847
binding = ActivitySplashBinding.inflate(layoutInflater)
4948
setContentView(binding.root)
5049

51-
WindowCompat.setDecorFitsSystemWindows(window, false)
52-
53-
// Kiểm tra khởi tạo nhanh
50+
// 1. Kiểm tra nếu script đã chạy hoặc đang chạy thì vào thẳng Home
5451
if (ScriptEnvironmen.isInited() && isTaskRoot &&
5552
!intent.getBooleanExtra("force_reset", false)) {
5653
gotoHome()
5754
return
5855
}
5956

60-
if (!hasAgreed()) {
57+
// 2. Logic khởi đầu: Nếu chưa đồng ý hoặc thiếu quyền thì hiện Dialog
58+
if (hasAgreed() && hasRequiredPermissions()) {
59+
checkPermissionsNextStep()
60+
} else {
6161
showAgreementDialog()
6262
}
6363

6464
binding.startLogoXml.startAnimation(AnimationUtils.loadAnimation(this, R.anim.ic_settings_rotate))
6565
applyTheme()
6666
}
6767

68-
// Luồng khởi động duy nhất
69-
private fun startLogic() {
70-
if (startingJob?.isActive == true) return // Nếu đang chạy thì không chạy thêm luồng mới
71-
72-
startingJob = lifecycleScope.launch(Dispatchers.Main) {
73-
saveAgreement()
74-
75-
// Bước 1: Kiểm tra Root với Timeout tránh treo cứng
76-
hasRoot = withContext(Dispatchers.IO) {
77-
try {
78-
withTimeoutOrNull(5000) { KeepShellPublic.checkRoot() } ?: false
79-
} catch (e: Exception) { false }
80-
}
81-
82-
// Bước 2: Chuẩn bị config và chạy script
83-
binding.startStateText.text = getString(R.string.pop_started)
84-
val config = KrScriptConfig().init(this@SplashActivity)
85-
86-
if (config.beforeStartSh.isNotEmpty()) {
87-
executeShellLogic(config, hasRoot)
88-
} else {
89-
gotoHome()
90-
}
91-
}
92-
}
93-
94-
private suspend fun executeShellLogic(config: KrScriptConfig, hasRoot: Boolean) {
95-
withContext(Dispatchers.IO) {
96-
var process: Process? = null
97-
try {
98-
process = if (hasRoot) ShellExecutor.getSuperUserRuntime() else ShellExecutor.getRuntime()
99-
process?.let { p ->
100-
// Đọc log song song
101-
val logJob = launch {
102-
p.inputStream.bufferedReader().useLines { lines ->
103-
lines.forEach { line ->
104-
withContext(Dispatchers.Main) { onLogOutput(line) }
105-
}
106-
}
107-
}
68+
// =================== LOGIC XỬ LÝ QUYỀN ===================
10869

109-
// Thực thi shell
110-
DataOutputStream(p.outputStream).use { os ->
111-
ScriptEnvironmen.executeShell(
112-
this@SplashActivity, os, config.beforeStartSh, config.variables, null, "pio-splash"
113-
)
114-
}
115-
116-
// Đợi tối đa 15s cho script
117-
withTimeoutOrNull(15000) { p.waitFor() }
118-
logJob.cancel()
119-
}
120-
} catch (e: Exception) {
121-
e.printStackTrace()
122-
} finally {
123-
process?.destroy() // Giải phóng tiến trình
124-
withContext(Dispatchers.Main) { gotoHome() }
125-
}
126-
}
70+
private fun showAgreementDialog() {
71+
DialogHelper.warning(
72+
this,
73+
getString(R.string.permission_dialog_title),
74+
getString(R.string.permission_dialog_message),
75+
Runnable {
76+
// Người dùng đồng ý bảng Dialog -> Chuyển sang xin quyền bộ nhớ hệ thống
77+
requestRequiredPermissions()
78+
},
79+
Runnable { finishAffinity() } // Không đồng ý -> Thoát ứng dụng
80+
).setCancelable(false)
12781
}
12882

129-
override fun onResume() {
130-
super.onResume()
131-
// Chỉ chạy nếu đã đồng ý và các quyền đã sẵn sàng
132-
if (hasAgreed() && startingJob == null && hasPermissions()) {
133-
startLogic()
83+
private fun hasRequiredPermissions(): Boolean {
84+
val permissions = mutableListOf<String>()
85+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
86+
permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
87+
permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
88+
} else {
89+
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
90+
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
13491
}
135-
}
136-
137-
// --- Giữ nguyên các hàm bổ trợ của bạn ---
138-
139-
private fun hasPermissions(): Boolean {
140-
val basic = getRequiredPermissions().all {
92+
return permissions.all {
14193
ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
14294
}
143-
val manage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Environment.isExternalStorageManager() else true
144-
return basic && manage
14595
}
14696

147-
private fun getRequiredPermissions(): Array<String> {
148-
return when {
149-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> arrayOf(Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO)
150-
else -> arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
97+
private fun requestRequiredPermissions() {
98+
val permissions = mutableListOf<String>()
99+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
100+
permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
101+
permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
102+
} else {
103+
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
104+
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
151105
}
106+
ActivityCompat.requestPermissions(this, permissions.toTypedArray(), REQUEST_CODE_PERMISSIONS)
152107
}
153108

154-
private fun requestAppPermissions() {
155-
val missing = getRequiredPermissions().filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
156-
if (missing.isNotEmpty()) {
157-
ActivityCompat.requestPermissions(this, missing.toTypedArray(), REQUEST_CODE_PERMISSIONS)
158-
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
159-
requestManageStoragePermission()
109+
private fun checkPermissionsNextStep() {
110+
// Ưu tiên sau: Nếu là Android 11+ và chưa có quyền "All Files Access"
111+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
112+
requestManageAllFilesPermission()
160113
} else {
161-
startLogic()
114+
checkRootAndStart()
162115
}
163116
}
164117

165-
private fun requestManageStoragePermission() {
166-
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { data = Uri.parse("package:$packageName") }
167-
try { startActivityForResult(intent, REQUEST_CODE_MANAGE_STORAGE) }
168-
catch (e: Exception) { startActivityForResult(Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION), REQUEST_CODE_MANAGE_STORAGE) }
118+
private fun requestManageAllFilesPermission() {
119+
try {
120+
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
121+
data = Uri.parse("package:$packageName")
122+
}
123+
startActivityForResult(intent, REQUEST_CODE_MANAGE_ALL_FILES)
124+
} catch (e: Exception) {
125+
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
126+
startActivityForResult(intent, REQUEST_CODE_MANAGE_ALL_FILES)
127+
}
169128
}
170129

171130
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
172131
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
173132
if (requestCode == REQUEST_CODE_PERMISSIONS) {
174-
if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) requestAppPermissions() else finish()
133+
// Nếu người dùng đồng ý các quyền bộ nhớ cơ bản
134+
if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {
135+
checkPermissionsNextStep()
136+
} else {
137+
// Nếu từ chối bất kỳ quyền nào -> Thoát app
138+
finishAffinity()
139+
}
175140
}
176141
}
177142

178143
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
179144
super.onActivityResult(requestCode, resultCode, data)
180-
if (requestCode == REQUEST_CODE_MANAGE_STORAGE) {
181-
if (hasPermissions()) startLogic() else finish()
145+
if (requestCode == REQUEST_CODE_MANAGE_ALL_FILES) {
146+
// Sau khi từ màn hình cài đặt All Files quay lại, tiến hành check root
147+
checkRootAndStart()
182148
}
183149
}
184150

185-
private fun onLogOutput(log: String) {
186-
synchronized(rows) {
187-
if (rows.size >= maxLines) { rows.removeAt(0); ignored = true }
188-
rows.add(log)
189-
binding.startStateText.text = rows.joinToString("\n", if (ignored) "……\n" else "")
151+
// =================== LOGIC ROOT & KHỞI CHẠY ===================
152+
153+
@Synchronized
154+
private fun checkRootAndStart() {
155+
if (starting) return
156+
starting = true
157+
started = true
158+
159+
// Khi đã vượt qua hết các bước xin quyền và bắt đầu check root -> Lưu trạng thái đồng ý
160+
saveAgreement()
161+
162+
lifecycleScope.launch(Dispatchers.IO) {
163+
hasRoot = KeepShellPublic.checkRoot()
164+
withContext(Dispatchers.Main) {
165+
starting = false
166+
startToFinish()
167+
}
168+
}
169+
}
170+
171+
private fun startToFinish() {
172+
binding.startStateText.text = getString(R.string.pop_started)
173+
val config = KrScriptConfig().init(this)
174+
175+
if (config.beforeStartSh.isNotEmpty()) {
176+
runBeforeStartSh(config, hasRoot)
177+
} else {
178+
gotoHome()
190179
}
191180
}
192181

182+
private fun hasAgreed(): Boolean =
183+
getSharedPreferences("kr-script-config", MODE_PRIVATE).getBoolean("agreed_permissions", false)
184+
185+
private fun saveAgreement() {
186+
getSharedPreferences("kr-script-config", MODE_PRIVATE)
187+
.edit()
188+
.putBoolean("agreed_permissions", true)
189+
.apply()
190+
}
191+
192+
// =================== CÁC HÀM TIỆN ÍCH KHÁC ===================
193+
193194
private fun applyAppLanguage() {
194195
runCatching {
195196
val langFile = File(filesDir, "home/log/language")
196-
val lang = langFile.takeIf { it.exists() }?.readText()?.trim() ?: return
197+
val lang = langFile.takeIf { it.exists() }?.readText()?.trim()?.takeIf { it.isNotEmpty() } ?: return
197198
val locale = Locale.forLanguageTag(lang.replace("_", "-"))
198199
if (Locale.getDefault() != locale) {
199200
Locale.setDefault(locale)
@@ -206,28 +207,69 @@ class SplashActivity : AppCompatActivity() {
206207
private fun applyTheme() {
207208
val typedValue = TypedValue()
208209
theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true)
209-
val bgColor = if (typedValue.resourceId != 0) ContextCompat.getColor(this, typedValue.resourceId) else typedValue.data
210+
val bgColor = if (typedValue.resourceId != 0) getColor(typedValue.resourceId) else typedValue.data
211+
212+
WindowCompat.setDecorFitsSystemWindows(window, false)
210213
window.statusBarColor = bgColor
211214
window.navigationBarColor = bgColor
212-
WindowCompat.getInsetsController(window, window.decorView).apply {
213-
val isDark = ThemeModeState.isDarkMode()
214-
isAppearanceLightStatusBars = !isDark
215-
isAppearanceLightNavigationBars = !isDark
215+
216+
val controller = WindowCompat.getInsetsController(window, window.decorView)
217+
val isDark = ThemeModeState.isDarkMode()
218+
controller.isAppearanceLightStatusBars = !isDark
219+
controller.isAppearanceLightNavigationBars = !isDark
220+
}
221+
222+
private fun gotoHome() {
223+
val intentToStart = if (intent?.getBooleanExtra("JumpActionPage", false) == true) {
224+
Intent(this, ActionPage::class.java).apply { putExtras(intent!!) }
225+
} else {
226+
Intent(this, MainActivity::class.java)
216227
}
228+
startActivity(intentToStart)
229+
finish()
217230
}
218231

219-
private fun showAgreementDialog() {
220-
DialogHelper.warning(this, getString(R.string.permission_dialog_title), getString(R.string.permission_dialog_message), { requestAppPermissions() }, { finish() }).setCancelable(false)
232+
private fun runBeforeStartSh(config: KrScriptConfig, hasRoot: Boolean) {
233+
lifecycleScope.launch(Dispatchers.IO) {
234+
try {
235+
val process = if (hasRoot) ShellExecutor.getSuperUserRuntime() else ShellExecutor.getRuntime()
236+
process?.let {
237+
DataOutputStream(it.outputStream).use { os ->
238+
ScriptEnvironmen.executeShell(this@SplashActivity, os, config.beforeStartSh, config.variables, null, "pio-splash")
239+
}
240+
launch { readStreamAsync(it.inputStream.bufferedReader()) }
241+
launch { readStreamAsync(it.errorStream.bufferedReader()) }
242+
it.waitFor()
243+
}
244+
} finally {
245+
withContext(Dispatchers.Main) {
246+
gotoHome()
247+
}
248+
}
249+
}
221250
}
222251

223-
private fun hasAgreed() = getSharedPreferences("kr-script-config", MODE_PRIVATE).getBoolean("agreed_permissions", false)
224-
private fun saveAgreement() = getSharedPreferences("kr-script-config", MODE_PRIVATE).edit().putBoolean("agreed_permissions", true).apply()
252+
private val rows = mutableListOf<String>()
253+
private var ignored = false
254+
private val maxLines = 5
255+
private val handler = android.os.Handler(android.os.Looper.getMainLooper())
225256

226-
private fun gotoHome() {
227-
val nextIntent = if (intent?.getBooleanExtra("JumpActionPage", false) == true) {
228-
Intent(this, ActionPage::class.java).apply { intent?.extras?.let { putExtras(it) } }
229-
} else Intent(this, MainActivity::class.java)
230-
startActivity(nextIntent)
231-
finish()
257+
private fun readStreamAsync(reader: BufferedReader) {
258+
try {
259+
reader.forEachLine { line -> onLogOutput(line) }
260+
} catch (e: Exception) {}
261+
}
262+
263+
private fun onLogOutput(log: String) {
264+
handler.post {
265+
synchronized(rows) {
266+
if (rows.size >= maxLines) {
267+
rows.removeAt(0)
268+
ignored = true
269+
}
270+
rows.add(log)
271+
binding.startStateText.text = rows.joinToString("\n", if (ignored) "……\n" else "")
272+
}
273+
}
232274
}
233275
}

0 commit comments

Comments
 (0)