Skip to content

Commit 32fd543

Browse files
committed
fix: self-heal BootReceiver on startup to survive aggressive OS pruning
Some smart display and TV OS implementations silently place background broadcast receivers into a disabledComponents list after reboot. This breaks the standard BOOT_COMPLETED broadcast and prevents the launcher service from starting unattended after hard power cuts. Added an initialization check in LauncherService.onCreate() that dynamically re-enables BootReceiver via PackageManager if it was tampered with by the OS, ensuring the web server will restart successfully on the next boot cycle. Ported fix from PR #17 (credit: @andornaut).
1 parent 1bb037c commit 32fd543

3 files changed

Lines changed: 67 additions & 1 deletion

File tree

.idea/appInsightsSettings.xml

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/markdown.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/java/com/tpn/displaylauncher/LauncherService.kt

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import android.app.NotificationChannel
55
import android.app.NotificationManager
66
import android.app.PendingIntent
77
import android.app.Service
8+
import android.content.ComponentName
89
import android.content.Context
910
import android.content.Intent
11+
import android.content.pm.PackageManager
1012
import android.os.Build
1113
import android.os.Handler
1214
import android.os.IBinder
@@ -47,9 +49,39 @@ class LauncherService : Service() {
4749

4850
override fun onBind(intent: Intent?): IBinder? = null
4951

52+
// Some devices disable BootReceiver behind the app's back, which takes it out
53+
// of the BOOT_COMPLETED resolution set and stops the app ever starting at
54+
// boot. A shell cannot undo that for an app that is not test-only, and
55+
// reinstalling loses the app's adb key, but the app may set its own
56+
// components, so it is repaired here on every service start.
57+
private fun ensureBootReceiverEnabled() {
58+
try {
59+
val receiver = ComponentName(this, BootReceiver::class.java)
60+
// Anything that is not enabled, rather than the one disabled state:
61+
// DISABLED_USER and DISABLED_UNTIL_USED keep it out of the resolution
62+
// set just as surely, and DEFAULT means the manifest value, enabled.
63+
val state = packageManager.getComponentEnabledSetting(receiver)
64+
65+
if (state != PackageManager.COMPONENT_ENABLED_STATE_ENABLED &&
66+
state != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
67+
68+
packageManager.setComponentEnabledSetting(
69+
receiver,
70+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
71+
PackageManager.DONT_KILL_APP
72+
)
73+
Log.i(TAG, "BootReceiver was disabled by OS. Re-enabled it.")
74+
}
75+
} catch (e: Exception) {
76+
Log.e(TAG, "Could not check or re-enable BootReceiver", e)
77+
}
78+
}
79+
5080
override fun onCreate() {
5181
super.onCreate()
5282
Log.d(TAG, "Service created")
83+
84+
ensureBootReceiverEnabled()
5385
createNotificationChannel()
5486

5587
// Start server monitoring once upon service creation
@@ -87,7 +119,7 @@ class LauncherService : Service() {
87119
// to prevent foreground service timeout crashes, but the OS will safely
88120
// suppress the visual notification if the runtime permission is denied.
89121
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
90-
checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != android.content.pm.PackageManager.PERMISSION_GRANTED) {
122+
checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
91123
Log.w(TAG, "POST_NOTIFICATIONS permission is not granted. Service will run, but notification display will be suppressed.")
92124
}
93125

0 commit comments

Comments
 (0)