11package com.tool.tree
22
3- import android.content.Context
3+ import android.Manifest
44import android.content.Intent
55import android.content.pm.PackageManager
66import android.content.res.Configuration
7- import android.graphics.Color
8- import android.net.Uri
97import android.os.Build
108import android.os.Bundle
11- import android.os.Environment
12- import android.os.Handler
13- import android.provider.Settings
9+ import android.util.TypedValue
1410import android.view.animation.AnimationUtils
15- import android.widget.TextView
1611import androidx.appcompat.app.AppCompatActivity
12+ import androidx.appcompat.app.AppCompatDelegate
1713import androidx.core.app.ActivityCompat
1814import androidx.core.content.ContextCompat
15+ import androidx.core.os.LocaleListCompat
1916import androidx.core.view.WindowCompat
20- import androidx.core.view.WindowInsetsControllerCompat
2117import androidx.lifecycle.lifecycleScope
2218import com.omarea.common.shell.KeepShellPublic
2319import com.omarea.common.shell.ShellExecutor
2420import com.omarea.common.ui.DialogHelper
2521import com.omarea.krscript.executor.ScriptEnvironmen
2622import com.tool.tree.databinding.ActivitySplashBinding
27- import com.tool.tree.R
2823import kotlinx.coroutines.Dispatchers
2924import kotlinx.coroutines.launch
3025import kotlinx.coroutines.withContext
3126import java.io.BufferedReader
3227import java.io.DataOutputStream
3328import java.io.File
34- import java.util.Locale
35- import android.util.TypedValue
3629
3730class SplashActivity : AppCompatActivity () {
3831
@@ -42,25 +35,36 @@ class SplashActivity : AppCompatActivity() {
4235 private var hasRoot = false
4336 private var started = false
4437 private var starting = false
38+
39+ private val rows = mutableListOf<String >()
40+ private var ignored = false
41+ private val maxLines = 5
4542
4643 override fun onCreate (savedInstanceState : Bundle ? ) {
44+ // 1. Áp dụng ngôn ngữ sớm nhất có thể
4745 applyAppLanguage()
4846 super .onCreate(savedInstanceState)
47+
4948 ThemeModeState .switchTheme(this )
5049 binding = ActivitySplashBinding .inflate(layoutInflater)
5150 setContentView(binding.root)
5251
52+ // 2. Hỗ trợ hiển thị tràn viền (Edge-to-Edge) cho các máy đời mới
53+ WindowCompat .setDecorFitsSystemWindows(window, false )
54+
5355 if (ScriptEnvironmen .isInited() && isTaskRoot &&
5456 ! intent.getBooleanExtra(" force_reset" , false )) {
5557 gotoHome()
5658 return
5759 }
5860
59- if (! hasAgreed()) showAgreementDialog()
61+ if (! hasAgreed()) {
62+ showAgreementDialog()
63+ }
6064
61- binding.startLogoXml.postDelayed({
62- binding.startLogoXml.startAnimation( AnimationUtils .loadAnimation(this , R .anim.blink) )
63- }, 2000 )
65+ val imageView = findViewById< ImageView >( R .id.start_logo_xml)
66+ val rotateAnim = AnimationUtils .loadAnimation(this , R .anim.ic_settings_rotate )
67+ imageView.startAnimation(rotateAnim )
6468
6569 applyTheme()
6670 }
@@ -69,154 +73,101 @@ class SplashActivity : AppCompatActivity() {
6973 runCatching {
7074 val langFile = File (filesDir, " home/log/language" )
7175 val lang = langFile.takeIf { it.exists() }?.readText()?.trim()?.takeIf { it.isNotEmpty() } ? : return
72- val locale = Locale .forLanguageTag(lang.replace(" _" , " -" ))
73- if (Locale .getDefault() != locale) {
74- Locale .setDefault(locale)
75- val config = Configuration (resources.configuration).apply { setLocale(locale) }
76- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N ) {
77- resources.updateConfiguration(config, resources.displayMetrics)
78- } else {
79- @Suppress(" DEPRECATION" )
80- resources.updateConfiguration(config, resources.displayMetrics)
81- }
82- }
76+
77+ // Cách chuẩn hỗ trợ từ SDK thấp đến cao
78+ val appLocale: LocaleListCompat = LocaleListCompat .forLanguageTags(lang.replace(" _" , " -" ))
79+ AppCompatDelegate .setApplicationLocales(appLocale)
8380 }
8481 }
8582
86- private fun getThemeColor (attr : Int ): Int {
87- val typedValue = TypedValue ()
88- theme.resolveAttribute(attr, typedValue, true )
89-
90- return if (typedValue.resourceId != 0 ) {
91- getColor(typedValue.resourceId)
92- } else {
93- typedValue.data
94- }
95- }
96-
9783 private fun applyTheme () {
98- val bgColor = getThemeColor(android.R .attr.windowBackground)
99-
84+ val typedValue = TypedValue ()
85+ theme.resolveAttribute(android.R .attr.windowBackground, typedValue, true )
86+ val bgColor = if (typedValue.resourceId != 0 ) ContextCompat .getColor(this , typedValue.resourceId) else typedValue.data
87+
10088 window.statusBarColor = bgColor
10189 window.navigationBarColor = bgColor
10290
10391 val controller = WindowCompat .getInsetsController(window, window.decorView)
104-
10592 val isDark = ThemeModeState .isDarkMode()
10693
107- controller?.isAppearanceLightStatusBars = ! isDark
108- controller?.isAppearanceLightNavigationBars = ! isDark
94+ // WindowInsetsControllerCompat tự động xử lý các bản Android cũ hơn (dưới SDK 30)
95+ controller.isAppearanceLightStatusBars = ! isDark
96+ controller.isAppearanceLightNavigationBars = ! isDark
10997 }
11098
111- // =================== AGREEMENT ===================
112- private fun showAgreementDialog () {
113- DialogHelper .warning(
114- this ,
115- getString(R .string.permission_dialog_title),
116- getString(R .string.permission_dialog_message),
117- Runnable { requestAppPermissions() },
118- Runnable { finish() }
119- ).setCancelable(false )
99+ // =================== PERMISSIONS (Hỗ trợ SDK 23+) ===================
100+ private fun getRequiredPermissions (): Array <String > {
101+ return when {
102+ // Android 13 (API 33) trở lên
103+ Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU -> {
104+ arrayOf(
105+ Manifest .permission.READ_MEDIA_IMAGES ,
106+ Manifest .permission.READ_MEDIA_VIDEO
107+ )
108+ }
109+ // Android 6.0 (API 23) đến Android 12
110+ else -> {
111+ arrayOf(Manifest .permission.READ_EXTERNAL_STORAGE )
112+ }
113+ }
120114 }
121115
122- private fun hasAgreed (): Boolean =
123- getSharedPreferences(" kr-script-config" , MODE_PRIVATE )
124- .getBoolean(" agreed_permissions" , false )
125-
126- private fun saveAgreement () {
127- getSharedPreferences(" kr-script-config" , MODE_PRIVATE )
128- .edit()
129- .putBoolean(" agreed_permissions" , true )
130- .apply ()
116+ private fun hasPermissions (): Boolean {
117+ return getRequiredPermissions().all {
118+ ContextCompat .checkSelfPermission(this , it) == PackageManager .PERMISSION_GRANTED
119+ }
131120 }
132121
133- // =================== PERMISSION ===================
134- private fun hasReadPermission () = ContextCompat .checkSelfPermission(this , android.Manifest .permission.READ_EXTERNAL_STORAGE ) == PackageManager .PERMISSION_GRANTED
135-
136- private fun requestReadPermission () = ActivityCompat .requestPermissions(this , arrayOf(android.Manifest .permission.READ_EXTERNAL_STORAGE ), REQUEST_CODE_PERMISSIONS )
137-
138122 private fun requestAppPermissions () {
139123 saveAgreement()
140- if (hasReadPermission()) {
141- started = true
142- checkRootAndStart()
143- } else requestReadPermission()
144- }
145-
146- override fun onResume () {
147- super .onResume()
148- if (hasAgreed() && ! started) {
124+ if (hasPermissions()) {
149125 started = true
150126 checkRootAndStart()
127+ } else {
128+ ActivityCompat .requestPermissions(this , getRequiredPermissions(), REQUEST_CODE_PERMISSIONS )
151129 }
152130 }
153131
154132 override fun onRequestPermissionsResult (requestCode : Int , permissions : Array <out String >, grantResults : IntArray ) {
133+ super .onRequestPermissionsResult(requestCode, permissions, grantResults)
155134 if (requestCode == REQUEST_CODE_PERMISSIONS ) {
156- if (grantResults.all { it == PackageManager .PERMISSION_GRANTED }) {
135+ // Kiểm tra xem tất cả quyền được cấp chưa
136+ if (grantResults.isNotEmpty() && grantResults.all { it == PackageManager .PERMISSION_GRANTED }) {
157137 started = true
158138 checkRootAndStart()
159- } else finish()
160- }
161- super .onRequestPermissionsResult(requestCode, permissions, grantResults)
162- }
163-
164- // =================== ROOT ===================
165- @Synchronized
166- private fun checkRootAndStart () {
167- if (! started || starting) return
168- starting = true
169-
170- lifecycleScope.launch(Dispatchers .IO ) {
171- hasRoot = KeepShellPublic .checkRoot()
172- withContext(Dispatchers .Main ) {
173- starting = false
174- startToFinish()
139+ } else {
140+ finish() // Hoặc thông báo yêu cầu quyền
175141 }
176142 }
177143 }
178144
179- private fun startToFinish () {
180- binding.startStateText.text = getString(R .string.pop_started)
181- val config = KrScriptConfig ().init (this )
182-
183- if (config.beforeStartSh.isNotEmpty()) {
184- runBeforeStartSh(config, hasRoot)
185- } else gotoHome()
186- }
187-
188- private fun gotoHome () {
189- startActivity(
190- if (intent?.getBooleanExtra(" JumpActionPage" , false ) == true )
191- Intent (this , ActionPage ::class .java).apply { putExtras(intent!! ) }
192- else Intent (this , MainActivity ::class .java)
193- )
194- finish()
195- }
196-
145+ // =================== SHELL & LOGS (Tối ưu Coroutines) ===================
197146 private fun runBeforeStartSh (config : KrScriptConfig , hasRoot : Boolean ) {
198147 lifecycleScope.launch(Dispatchers .IO ) {
199148 try {
200- val process = if (hasRoot)
201- ShellExecutor .getSuperUserRuntime()
202- else
203- ShellExecutor .getRuntime()
204- process?.let {
205- DataOutputStream (it.outputStream).use { os ->
206- ScriptEnvironmen .executeShell(
207- this @SplashActivity,
208- os,
209- config.beforeStartSh,
210- config.variables,
211- null ,
212- " pio-splash"
213- )
149+ val process = if (hasRoot) ShellExecutor .getSuperUserRuntime() else ShellExecutor .getRuntime()
150+ process?.let { p ->
151+ // Thực thi lệnh trong một job riêng
152+ val shellJob = launch(Dispatchers .IO ) {
153+ DataOutputStream (p.outputStream).use { os ->
154+ ScriptEnvironmen .executeShell(
155+ this @SplashActivity, os, config.beforeStartSh, config.variables, null , " pio-splash"
156+ )
157+ }
214158 }
215- launch { readStreamAsync(it.inputStream.bufferedReader()) }
216- launch { readStreamAsync(it.errorStream.bufferedReader()) }
217-
218- it.waitFor()
159+
160+ // Đọc log đồng thời (hỗ trợ SDK 23+)
161+ val outJob = launch { readStreamAsync(p.inputStream.bufferedReader()) }
162+ val errJob = launch { readStreamAsync(p.errorStream.bufferedReader()) }
163+
164+ p.waitFor()
165+ shellJob.join()
166+ outJob.join()
167+ errJob.join()
219168 }
169+ } catch (e: Exception ) {
170+ e.printStackTrace()
220171 } finally {
221172 withContext(Dispatchers .Main ) {
222173 gotoHome()
@@ -225,30 +176,62 @@ class SplashActivity : AppCompatActivity() {
225176 }
226177 }
227178
228- // Buffer lưu 4 dòng cuối
229- private val rows = mutableListOf<String >()
230- private var ignored = false
231- private val maxLines = 5
232- private val handler = android.os.Handler (android.os.Looper .getMainLooper())
233-
234- private fun readStreamAsync (reader : BufferedReader ) {
235- Thread {
236- reader.forEachLine { line ->
237- onLogOutput(line)
179+ private suspend fun readStreamAsync (reader : BufferedReader ) {
180+ withContext(Dispatchers .IO ) {
181+ reader.useLines { lines ->
182+ lines.forEach { line ->
183+ withContext(Dispatchers .Main ) {
184+ onLogOutput(line)
185+ }
186+ }
238187 }
239- }.start()
188+ }
240189 }
241190
242191 private fun onLogOutput (log : String ) {
243- handler.post {
244- synchronized(rows) {
245- if (rows.size >= maxLines) {
246- rows.removeAt(0 )
247- ignored = true
248- }
249- rows.add(log)
250- binding.startStateText.text = rows.joinToString(" \n " , if (ignored) " ……\n " else " " )
192+ synchronized(rows) {
193+ if (rows.size >= maxLines) {
194+ rows.removeAt(0 )
195+ ignored = true
251196 }
197+ rows.add(log)
198+ binding.startStateText.text = rows.joinToString(" \n " , if (ignored) " ……\n " else " " )
252199 }
253200 }
254- }
201+
202+ // Các hàm phụ trợ khác giữ nguyên logic của bạn...
203+ private fun showAgreementDialog () {
204+ DialogHelper .warning(this , getString(R .string.permission_dialog_title), getString(R .string.permission_dialog_message), { requestAppPermissions() }, { finish() }).setCancelable(false )
205+ }
206+
207+ private fun hasAgreed () = getSharedPreferences(" kr-script-config" , MODE_PRIVATE ).getBoolean(" agreed_permissions" , false )
208+
209+ private fun saveAgreement () = getSharedPreferences(" kr-script-config" , MODE_PRIVATE ).edit().putBoolean(" agreed_permissions" , true ).apply ()
210+
211+ @Synchronized
212+ private fun checkRootAndStart () {
213+ if (! started || starting) return
214+ starting = true
215+ lifecycleScope.launch(Dispatchers .IO ) {
216+ hasRoot = KeepShellPublic .checkRoot()
217+ withContext(Dispatchers .Main ) {
218+ starting = false
219+ startToFinish()
220+ }
221+ }
222+ }
223+
224+ private fun startToFinish () {
225+ binding.startStateText.text = getString(R .string.pop_started)
226+ val config = KrScriptConfig ().init (this )
227+ if (config.beforeStartSh.isNotEmpty()) runBeforeStartSh(config, hasRoot) else gotoHome()
228+ }
229+
230+ private fun gotoHome () {
231+ val nextIntent = if (intent?.getBooleanExtra(" JumpActionPage" , false ) == true ) {
232+ Intent (this , ActionPage ::class .java).apply { intent?.extras?.let { putExtras(it) } }
233+ } else Intent (this , MainActivity ::class .java)
234+ startActivity(nextIntent)
235+ finish()
236+ }
237+ }
0 commit comments