Skip to content

Commit 00d2503

Browse files
committed
Auto-start the floating button on boot
Head units power-cycle with the car's ignition, so requiring a manual tap into the app every time is the wrong default. A BOOT_COMPLETED receiver now restarts the service automatically if overlay + accessibility permissions are already granted. Extracted the permission checks (previously private to MainActivity) into PermissionUtils so BootReceiver doesn't duplicate them.
1 parent 62f8bdc commit 00d2503

5 files changed

Lines changed: 57 additions & 11 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ no need to build it yourself. Sideload it (enable "install from unknown sources"
2626
- **Tap to sleep** – single tap locks/sleeps screen immediately
2727
- **Haptic feedback** – subtle vibration confirms the tap
2828
- **Persistent service** – survives app switching; notification with quick Stop action
29+
- **Auto-start on boot** – if both permissions are already granted, the button comes back
30+
automatically after the head unit reboots
2931
- **Dark space UI** – minimal setup screen
3032

3133
---

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
<uses-permission android:name="android.permission.VIBRATE" />
1313
<!-- Post notification for foreground service (Android 13+) -->
1414
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
15+
<!-- Auto-start the button after the head unit reboots -->
16+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
1517

1618
<application
1719
android:allowBackup="false"
@@ -42,6 +44,15 @@
4244
android:value="Persistent floating sleep button overlay" />
4345
</service>
4446

47+
<!-- Auto-start receiver -->
48+
<receiver
49+
android:name=".BootReceiver"
50+
android:exported="true">
51+
<intent-filter>
52+
<action android:name="android.intent.action.BOOT_COMPLETED" />
53+
</intent-filter>
54+
</receiver>
55+
4556
<!-- Accessibility service for screen lock -->
4657
<service
4758
android:name=".LockScreenService"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.sleepbutton.app
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.os.Build
7+
8+
/** Auto-starts the floating button after boot, if both permissions are already granted. */
9+
class BootReceiver : BroadcastReceiver() {
10+
11+
override fun onReceive(context: Context, intent: Intent) {
12+
if (intent.action != Intent.ACTION_BOOT_COMPLETED) return
13+
if (!PermissionUtils.hasAllPermissions(context)) return
14+
15+
val serviceIntent = Intent(context, FloatingButtonService::class.java)
16+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
17+
context.startForegroundService(serviceIntent)
18+
} else {
19+
context.startService(serviceIntent)
20+
}
21+
}
22+
}

app/src/main/java/com/sleepbutton/app/MainActivity.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package com.sleepbutton.app
22

33
import android.Manifest
4-
import android.accessibilityservice.AccessibilityServiceInfo
5-
import android.content.Context
64
import android.content.Intent
75
import android.content.pm.PackageManager
86
import android.net.Uri
97
import android.os.Build
108
import android.os.Bundle
119
import android.provider.Settings
12-
import android.view.accessibility.AccessibilityManager
1310
import android.widget.Button
1411
import android.widget.TextView
1512
import androidx.activity.result.contract.ActivityResultContracts
@@ -75,8 +72,8 @@ class MainActivity : AppCompatActivity() {
7572
}
7673

7774
private fun refreshUi() {
78-
val hasOverlay = Settings.canDrawOverlays(this)
79-
val hasAccessibility = isAccessibilityEnabled()
75+
val hasOverlay = PermissionUtils.hasOverlayPermission(this)
76+
val hasAccessibility = PermissionUtils.hasAccessibilityPermission(this)
8077
val allGranted = hasOverlay && hasAccessibility
8178

8279
btnOverlay.text = if (hasOverlay) getString(R.string.granted) else getString(R.string.grant)
@@ -105,10 +102,4 @@ class MainActivity : AppCompatActivity() {
105102
btnToggle.backgroundTintList = getColorStateList(R.color.colorAccent)
106103
}
107104
}
108-
109-
private fun isAccessibilityEnabled(): Boolean {
110-
val am = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
111-
val services = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
112-
return services.any { it.resolveInfo.serviceInfo.packageName == packageName }
113-
}
114105
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.sleepbutton.app
2+
3+
import android.accessibilityservice.AccessibilityServiceInfo
4+
import android.content.Context
5+
import android.provider.Settings
6+
import android.view.accessibility.AccessibilityManager
7+
8+
object PermissionUtils {
9+
10+
fun hasOverlayPermission(context: Context): Boolean = Settings.canDrawOverlays(context)
11+
12+
fun hasAccessibilityPermission(context: Context): Boolean {
13+
val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
14+
val services = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
15+
return services.any { it.resolveInfo.serviceInfo.packageName == context.packageName }
16+
}
17+
18+
fun hasAllPermissions(context: Context): Boolean =
19+
hasOverlayPermission(context) && hasAccessibilityPermission(context)
20+
}

0 commit comments

Comments
 (0)