@@ -3,171 +3,31 @@ package io.github.vvb2060.ims
33import android.content.BroadcastReceiver
44import android.content.Context
55import android.content.Intent
6- import android.content.pm.PackageManager
7- import android.os.Handler
8- import android.os.Looper
96import android.util.Log
10- import android.widget.Toast
11- import kotlinx.coroutines.CoroutineScope
12- import kotlinx.coroutines.Dispatchers
13- import kotlinx.coroutines.Job
14- import kotlinx.coroutines.delay
15- import kotlinx.coroutines.launch
16- import kotlinx.coroutines.withContext
17- import rikka.shizuku.Shizuku
7+ import androidx.work.OneTimeWorkRequest
8+ import androidx.work.WorkManager
189
1910/* *
20- * BootReceiver listens for device boot and Shizuku binder availability.
21- * Once Shizuku is ready, it automatically applies saved configurations to all SIM cards.
22- *
23- * Note: This receiver is triggered infrequently (only on boot), and the coroutine scope
24- * is designed to outlive the receiver's onReceive() context. The static job reference
25- * ensures we can cancel any previous attempts if multiple boot events occur.
11+ * BootReceiver listens for device boot and schedules a BootWorker to wait for Shizuku and apply
12+ * saved configurations.
2613 */
2714class BootReceiver : BroadcastReceiver () {
2815 companion object {
2916 private const val TAG = " BootReceiver"
30- private const val MAX_SHIZUKU_WAIT_TIME_MS = 60000L // 60 seconds
31- private const val SHIZUKU_CHECK_INTERVAL_MS = 1000L // 1 second
32-
33- // Static job to track ongoing configuration application
34- // Synchronized to prevent race conditions from multiple boot events
35- @Volatile
36- private var applyJob: Job ? = null
3717 }
3818
3919 override fun onReceive (context : Context , intent : Intent ) {
4020 when (intent.action) {
4121 Intent .ACTION_BOOT_COMPLETED , Intent .ACTION_LOCKED_BOOT_COMPLETED -> {
42- Log .i(TAG , " Boot completed, waiting for Shizuku to start " )
43- waitForShizukuAndApplyConfigs (context)
22+ Log .i(TAG , " Boot completed, enqueuing BootWorker " )
23+ enqueueBootWork (context)
4424 }
4525 }
4626 }
4727
48- /* *
49- * Waits for Shizuku to become available and then applies saved configurations.
50- * Uses a coroutine that outlives the receiver's onReceive() lifecycle.
51- */
52- private fun waitForShizukuAndApplyConfigs (context : Context ) {
53- // Use application context to ensure validity beyond receiver lifecycle
54- val appContext = context.applicationContext
55-
56- // Synchronized cancellation and creation to prevent race conditions
57- synchronized(BootReceiver ::class .java) {
58- applyJob?.cancel()
59-
60- applyJob = CoroutineScope (Dispatchers .IO ).launch {
61- var elapsedTime = 0L
62-
63- // Wait for Shizuku to be ready
64- while (elapsedTime < MAX_SHIZUKU_WAIT_TIME_MS ) {
65- if (Shizuku .pingBinder() &&
66- Shizuku .checkSelfPermission() == PackageManager .PERMISSION_GRANTED ) {
67- Log .i(TAG , " Shizuku is ready after ${elapsedTime} ms" )
68- applyAllSavedConfigurations(appContext)
69- return @launch
70- }
71-
72- delay(SHIZUKU_CHECK_INTERVAL_MS )
73- elapsedTime + = SHIZUKU_CHECK_INTERVAL_MS
74- }
75-
76- Log .w(TAG , " Timeout waiting for Shizuku to start after ${MAX_SHIZUKU_WAIT_TIME_MS } ms" )
77- }
78- }
79- }
80-
81- /* *
82- * Applies saved configurations to all SIM cards that have saved preferences.
83- */
84- private suspend fun applyAllSavedConfigurations (context : Context ) {
85- var successCount = 0
86- var failureCount = 0
87-
88- try {
89- // Read all available SIM cards
90- val simList = ShizukuProvider .readSimInfoList(context)
91- Log .i(TAG , " Found ${simList.size} SIM cards" )
92-
93- if (simList.isEmpty()) {
94- Log .w(TAG , " No SIM cards found, skipping configuration application" )
95- return
96- }
97-
98- // Apply saved configuration for each SIM card that has one
99- for (sim in simList) {
100- val savedConfig = SimConfigManager .loadConfiguration(context, sim.subId)
101- if (savedConfig != null ) {
102- Log .i(TAG , " Applying saved configuration for SIM ${sim.subId} (${sim.displayName} )" )
103- val resultMsg = SimConfigManager .applyConfiguration(context, sim.subId, savedConfig)
104- if (resultMsg == null ) {
105- Log .i(TAG , " Successfully applied configuration for SIM ${sim.subId} " )
106- successCount++
107- } else {
108- Log .e(TAG , " Failed to apply configuration for SIM ${sim.subId} : $resultMsg " )
109- failureCount++
110- }
111- } else {
112- Log .d(TAG , " No saved configuration found for SIM ${sim.subId} (${sim.displayName} )" )
113- }
114- }
115-
116- Log .i(TAG , " Finished applying saved configurations on boot" )
117-
118- // Show toast notification on main thread
119- if (successCount > 0 || failureCount > 0 ) {
120- withContext(Dispatchers .Main ) {
121- showToast(context, successCount, failureCount)
122- }
123- }
124- } catch (e: Exception ) {
125- Log .e(TAG , " Error applying saved configurations on boot" , e)
126- withContext(Dispatchers .Main ) {
127- showErrorToast(context, e.message ? : " Unknown error" )
128- }
129- }
130- }
131-
132- /* *
133- * Shows a toast message on the main thread with configuration results.
134- */
135- private fun showToast (context : Context , successCount : Int , failureCount : Int ) {
136- val mainHandler = Handler (Looper .getMainLooper())
137- var runnable: Runnable
138-
139- runnable = object : Runnable {
140- override fun run () {
141- val message = when {
142- failureCount == 0 && successCount > 0 ->
143- context.getString(R .string.config_success_message)
144- failureCount > 0 && successCount == 0 ->
145- context.getString(R .string.config_all_failed)
146- else ->
147- context.getString(R .string.config_mixed_result, successCount, failureCount)
148- }
149-
150- Toast .makeText(context, message, Toast .LENGTH_LONG ).show()
151- }
152- }
153-
154- mainHandler.post(runnable)
155- }
156-
157- /* *
158- * Shows an error toast on the main thread.
159- */
160- private fun showErrorToast (context : Context , error : String ) {
161- val mainHandler = Handler (Looper .getMainLooper())
162- var runnable: Runnable
163-
164- runnable = object : Runnable {
165- override fun run () {
166- val message = context.getString(R .string.config_failed, error)
167- Toast .makeText(context, message, Toast .LENGTH_LONG ).show()
168- }
169- }
28+ private fun enqueueBootWork (context : Context ) {
29+ val workRequest = OneTimeWorkRequest .Builder (BootWorker ::class .java).build()
17030
171- mainHandler.post(runnable )
31+ WorkManager .getInstance(context).enqueue(workRequest )
17232 }
17333}
0 commit comments