I have written and published an app “Sensor Recording” which reads out and displays the measured values of some sensors (position, acceleration, orientation, microphone, etc.). The app records sensor data continuously in the background, even if the display is switched OFF (standby mode). For this feature, I have established a Service, which is started by user interaction (button click). As of Android version 12, there must be some kind of prerequisites in the code and in the Manifest, which I have fulfilled. Here is a code snippet from “SensorService.java”:
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// ...
// ...
String channelId = "4711"; // arbitrary
Intent notificationIntent = new Intent(this, SensorService.class);
int flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, flag);
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) // O = OREO = 26
{
NotificationChannel channel = new NotificationChannel(channelId,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH);
notMan = getSystemService(NotificationManager.class);
if (notMan != null)
{
notMan.createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(this, channelId);
builder.setSmallIcon(R.drawable.vector3d_bg_transp)
.setContentTitle(text0)
.setContentText(text1)
.setTicker(text1)
.setSubText("Start Service")
.setShowWhen(true)
.setAutoCancel(true)
.setTimeoutAfter(500)
.setChannelId(channelId)
.setDefaults(Notification.DEFAULT_ALL)
.setPriority(Notification.PRIORITY_HIGH)
.setCategory(Notification.CATEGORY_MESSAGE)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent);
notification = builder.build();
int serviceType = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) // Q = Quince Tart = 29
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) // R = Red Velvet Cake = 30
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
ServiceCompat.startForeground(this, id, notification, serviceType);
}
}
else
{
// ...
}
return START_REDELIVER_INTENT;
}
and an extract from “AndroidManifest.xml”:
<manifest ...>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<!--
...
...
-->
<service android:name=".SensorService"
android:foregroundServiceType="location|microphone"
android:exported="false" />
</application>
</manifest>
This works fine on the four devices / Android versions which I have tested:
However, in the Google Developer Console (Section Android Vitals, Crashes and ANRs), I get frequent error messages from other devices (with Android 14 or 15), on which I have no physical access:
The error always occurs in the line
ServiceCompat.startForeground(this, id, notification, serviceType);
and is either “SecurityException“ or “ForegroundServiceStartNotAllowedException”. How can I locate the source of the errors?
Here is an example Stacktrace for "SecurityException":
Exception java.lang.RuntimeException: at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:4952) at android.app.ActivityThread.-$$Nest$mhandleServiceArgs (Unknown Source) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2381) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:205)
at android.os.Looper.loop (Looper.java:294) at android.app.ActivityThread.main (ActivityThread.java:8376) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:640) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:982) Caused by java.lang.SecurityException: at android.os.Parcel.createExceptionOrNull (Parcel.java:3057) at android.os.Parcel.createException (Parcel.java:3041) at android.os.Parcel.readException (Parcel.java:3024) at android.os.Parcel.readException (Parcel.java:2966) at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:6842) at android.app.Service.startForeground (Service.java:862) at androidx.core.app.ServiceCompat$Api34Impl.startForeground (ServiceCompat.java:241) at androidx.core.app.ServiceCompat.startForeground (ServiceCompat.java:172) at net.braun_home.sensorrecording.SensorService.onStartCommand (SensorService.java:549) at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:4934) Caused by android.os.RemoteException: Remote stack trace: at com.android.server.am.ActiveServices.validateForegroundServiceType (ActiveServices.java:2690) at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked (ActiveServices.java:2401) at com.android.server.am.ActiveServices.setServiceForegroundLocked (ActiveServices.java:1748) at com.android.server.am.ActivityManagerService.setServiceForeground (ActivityManagerService.java:13460) at android.app.IActivityManager$Stub.onTransact (IActivityManager.java:3422)
and a Stacktrace for "ForegroundServiceStartNotAllowedException":
Exception java.lang.RuntimeException: at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5286) at android.app.ActivityThread.-$$Nest$mhandleServiceArgs (Unknown Source) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2531) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:230)
at android.os.Looper.loop (Looper.java:319) at android.app.ActivityThread.main (ActivityThread.java:8919) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:578) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103) Caused by android.app.ForegroundServiceStartNotAllowedException: at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:54) at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:50) at android.os.Parcel.readParcelableInternal (Parcel.java:4882) at android.os.Parcel.readParcelable (Parcel.java:4864) at android.os.Parcel.createExceptionOrNull (Parcel.java:3064) at android.os.Parcel.createException (Parcel.java:3053) at android.os.Parcel.readException (Parcel.java:3036) at android.os.Parcel.readException (Parcel.java:2978) at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:7234) at android.app.Service.startForeground (Service.java:862) at androidx.core.app.ServiceCompat$Api34Impl.startForeground (ServiceCompat.java:241) at androidx.core.app.ServiceCompat.startForeground (ServiceCompat.java:172) at net.braun_home.sensorrecording.SensorService.onStartCommand (SensorService.java:549) at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5268)
I have written and published an app “Sensor Recording” which reads out and displays the measured values of some sensors (position, acceleration, orientation, microphone, etc.). The app records sensor data continuously in the background, even if the display is switched OFF (standby mode). For this feature, I have established a Service, which is started by user interaction (button click). As of Android version 12, there must be some kind of prerequisites in the code and in the Manifest, which I have fulfilled. Here is a code snippet from “SensorService.java”:
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// ...
// ...
String channelId = "4711"; // arbitrary
Intent notificationIntent = new Intent(this, SensorService.class);
int flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, flag);
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) // O = OREO = 26
{
NotificationChannel channel = new NotificationChannel(channelId,
getString(R.string.app_name),
NotificationManager.IMPORTANCE_HIGH);
notMan = getSystemService(NotificationManager.class);
if (notMan != null)
{
notMan.createNotificationChannel(channel);
Notification.Builder builder = new Notification.Builder(this, channelId);
builder.setSmallIcon(R.drawable.vector3d_bg_transp)
.setContentTitle(text0)
.setContentText(text1)
.setTicker(text1)
.setSubText("Start Service")
.setShowWhen(true)
.setAutoCancel(true)
.setTimeoutAfter(500)
.setChannelId(channelId)
.setDefaults(Notification.DEFAULT_ALL)
.setPriority(Notification.PRIORITY_HIGH)
.setCategory(Notification.CATEGORY_MESSAGE)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent);
notification = builder.build();
int serviceType = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) // Q = Quince Tart = 29
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) // R = Red Velvet Cake = 30
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
ServiceCompat.startForeground(this, id, notification, serviceType);
}
}
else
{
// ...
}
return START_REDELIVER_INTENT;
}
and an extract from “AndroidManifest.xml”:
<manifest ...>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<!--
...
...
-->
<service android:name=".SensorService"
android:foregroundServiceType="location|microphone"
android:exported="false" />
</application>
</manifest>
This works fine on the four devices / Android versions which I have tested:
However, in the Google Developer Console (Section Android Vitals, Crashes and ANRs), I get frequent error messages from other devices (with Android 14 or 15), on which I have no physical access:
The error always occurs in the line
ServiceCompat.startForeground(this, id, notification, serviceType);
and is either “SecurityException“ or “ForegroundServiceStartNotAllowedException”. How can I locate the source of the errors?
Here is an example Stacktrace for "SecurityException":
Exception java.lang.RuntimeException: at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:4952) at android.app.ActivityThread.-$$Nest$mhandleServiceArgs (Unknown Source) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2381) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:205)
at android.os.Looper.loop (Looper.java:294) at android.app.ActivityThread.main (ActivityThread.java:8376) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:640) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:982) Caused by java.lang.SecurityException: at android.os.Parcel.createExceptionOrNull (Parcel.java:3057) at android.os.Parcel.createException (Parcel.java:3041) at android.os.Parcel.readException (Parcel.java:3024) at android.os.Parcel.readException (Parcel.java:2966) at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:6842) at android.app.Service.startForeground (Service.java:862) at androidx.core.app.ServiceCompat$Api34Impl.startForeground (ServiceCompat.java:241) at androidx.core.app.ServiceCompat.startForeground (ServiceCompat.java:172) at net.braun_home.sensorrecording.SensorService.onStartCommand (SensorService.java:549) at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:4934) Caused by android.os.RemoteException: Remote stack trace: at com.android.server.am.ActiveServices.validateForegroundServiceType (ActiveServices.java:2690) at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked (ActiveServices.java:2401) at com.android.server.am.ActiveServices.setServiceForegroundLocked (ActiveServices.java:1748) at com.android.server.am.ActivityManagerService.setServiceForeground (ActivityManagerService.java:13460) at android.app.IActivityManager$Stub.onTransact (IActivityManager.java:3422)
and a Stacktrace for "ForegroundServiceStartNotAllowedException":
Exception java.lang.RuntimeException: at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5286) at android.app.ActivityThread.-$$Nest$mhandleServiceArgs (Unknown Source) at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2531) at android.os.Handler.dispatchMessage (Handler.java:106) at android.os.Looper.loopOnce (Looper.java:230)
at android.os.Looper.loop (Looper.java:319) at android.app.ActivityThread.main (ActivityThread.java:8919) at java.lang.reflect.Method.invoke at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:578) at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103) Caused by android.app.ForegroundServiceStartNotAllowedException: at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:54) at android.app.ForegroundServiceStartNotAllowedException$1.createFromParcel (ForegroundServiceStartNotAllowedException.java:50) at android.os.Parcel.readParcelableInternal (Parcel.java:4882) at android.os.Parcel.readParcelable (Parcel.java:4864) at android.os.Parcel.createExceptionOrNull (Parcel.java:3064) at android.os.Parcel.createException (Parcel.java:3053) at android.os.Parcel.readException (Parcel.java:3036) at android.os.Parcel.readException (Parcel.java:2978) at android.app.IActivityManager$Stub$Proxy.setServiceForeground (IActivityManager.java:7234) at android.app.Service.startForeground (Service.java:862) at androidx.core.app.ServiceCompat$Api34Impl.startForeground (ServiceCompat.java:241) at androidx.core.app.ServiceCompat.startForeground (ServiceCompat.java:172) at net.braun_home.sensorrecording.SensorService.onStartCommand (SensorService.java:549) at android.app.ActivityThread.handleServiceArgs (ActivityThread.java:5268)
After many days of investigations and tests without success, I decided to ask ChatGPT. And guess what? It came up with an error analysis:
The reason for the exceptions was not the device or Android version. The problem was the following: Some users obviously did not always grant the required permissions, e.g. for location or microphone. In Android 14+ this leads to the exceptions mentioned above, if the missing permissions are not handled properly. Solution:
Code snippet SensorService.java:
// handling of permissions
int serviceType = FOREGROUND_SERVICE_TYPE_NONE; // = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) // = 34 (Android 14)
{
// a) location
if ((checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED) ||
(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) ||
(checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED))
{
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
}
// b) microphone
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED)
{
serviceType |= ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
}
if (serviceType == FOREGROUND_SERVICE_TYPE_NONE) // nothing granted?
{
serviceType = FOREGROUND_SERVICE_TYPE_SPECIAL_USE; // substitute
}
}
// now start the service
ServiceCompat.startForeground(this, id, notification, serviceType);
Code snippet AndroidManifest.xml:
<manifest ...>
<application
<!--
...
...
-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
<!--
...
...
-->
<service android:name=".SensorService"
android:foregroundServiceType="location|microphone|specialUse"
android:exported="false">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value=
"This app records data from all smartphone sensors - including location and microphone.
These two sensors require the related permission from the user.
The method startForeground() is then started with the corresponding serviceType.
If none of these two permissions is granted, the serviceType would be 0, which would lead to an Exception.
To avoid this, the FOREGROUND_SERVICE_TYPE_SPECIAL_USE is taken as a substitute."/>
</service>
</application>
</manifest>
If the user does not grant the permissions for neither LOCATION nor MICROPHONE, there has to be a placeholder SPECIAL_USE to avoid exceptions. By the way: Its use must be justified to Google before publication in the Play Store (see AndroidManifest.xml, section property).
Google doesn't make life easy for developers (sigh!), does it? And a meaningful error message (such as MissingPermissionsException) would have helped to quickly find the cause.