11package de.klimek.compass
22
3+ import android.app.ForegroundServiceStartNotAllowedException
34import android.app.NotificationManager
4- import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
55import android.graphics.drawable.Icon
66import android.hardware.Sensor
77import android.hardware.SensorEvent
88import android.hardware.SensorEventListener
99import android.hardware.SensorManager
1010import android.hardware.display.DisplayManager
11- import android.os.Build
11+ import android.os.Build.VERSION
12+ import android.os.Build.VERSION_CODES
1213import android.service.quicksettings.Tile
1314import android.util.Log
1415import android.view.Display
@@ -21,9 +22,16 @@ import de.klimek.compass.tile.update
2122private const val TAG = " TileService"
2223private const val SENSOR_DELAY = SensorManager .SENSOR_DELAY_UI
2324
25+ // Note: Sensor data is only accessible in foreground, so we need to start this service as a foreground service.
26+ // This is done either on onCreate, onClick or onStartListening, depending on Android version.
27+
2428// On Android 14 foreground service can not be started in onClick or onStartListening due to a bug:
2529// https://issuetracker.google.com/issues/299506164
26- private val START_FOREGROUND_IMMEDIATELY = Build .VERSION .SDK_INT == Build .VERSION_CODES .UPSIDE_DOWN_CAKE
30+ private val START_FOREGROUND_IMMEDIATELY = VERSION .SDK_INT == VERSION_CODES .UPSIDE_DOWN_CAKE
31+
32+ // On Android 15+ foreground service can only be started after user interaction (onClick or sometimes onStartListening):
33+ // https://developer.android.com/about/versions/15/behavior-changes-15#fgs-hardening
34+ private val CAN_ONLY_START_FOREGROUND_ON_CLICK = VERSION .SDK_INT >= VERSION_CODES .VANILLA_ICE_CREAM
2735
2836class TileService : android.service.quicksettings.TileService (), SensorEventListener {
2937
@@ -57,7 +65,7 @@ class TileService : android.service.quicksettings.TileService(), SensorEventList
5765 iconFactory = IconFactory (applicationContext, R .drawable.ic_qs_compass_on)
5866 notificationManager?.createNotificationChannel(channel())
5967 if (START_FOREGROUND_IMMEDIATELY ) {
60- startForeground (NOTIFICATION_ID , notification(), FOREGROUND_SERVICE_TYPE_MANIFEST )
68+ startForegroundCompat (NOTIFICATION_ID , notification())
6169 }
6270 }
6371
@@ -68,12 +76,14 @@ class TileService : android.service.quicksettings.TileService(), SensorEventList
6876 }
6977
7078 override fun onStartListening () {
79+ Log .i(TAG , " Start listening" )
7180 when (qsTile?.state) {
7281 Tile .STATE_ACTIVE -> startCompass()
7382 }
7483 }
7584
7685 override fun onStopListening () {
86+ Log .i(TAG , " Stop listening" )
7787 when (qsTile?.state) {
7888 Tile .STATE_ACTIVE -> stopCompass()
7989 }
@@ -109,12 +119,20 @@ class TileService : android.service.quicksettings.TileService(), SensorEventList
109119 }
110120
111121 private fun startCompass () {
112- Log .i(TAG , " Start" )
113- // Sensor data is only accessible in foreground, so we need to start this service as a foreground service.
114- if (! START_FOREGROUND_IMMEDIATELY ) {
115- startForeground(NOTIFICATION_ID , notification())
122+ try {
123+ Log .i(TAG , " Start" )
124+ if (! START_FOREGROUND_IMMEDIATELY ) {
125+ startForegroundCompat(NOTIFICATION_ID , notification())
126+ }
127+ sensorManager?.registerListener(this , sensor, SENSOR_DELAY )
128+ } catch (e: Exception ) {
129+ if (CAN_ONLY_START_FOREGROUND_ON_CLICK && e is ForegroundServiceStartNotAllowedException ) {
130+ Log .w(TAG , " Foreground service start not allowed" , e)
131+ setInactive()
132+ } else {
133+ throw e // Crash on other exceptions
134+ }
116135 }
117- sensorManager?.registerListener(this , sensor, SENSOR_DELAY )
118136 }
119137
120138 private fun stopCompass () {
0 commit comments