oziomajnr
oziomajnr

Reputation: 1741

Context.startForegroundService() did not then call Service.startForeground() only on Android 11 Samsung devices

I am getting this crash for only Samsung devices running android 11. Apparently the application is calling startForegroundService(intent) and requiring me to post a notification for the user to know that I am running a foreground service, but this call to startForegroundService(intent) was never made in the app source code, is it possible that Samsung made a custom implementation of android 11 and automatically calls startForegroundService(intent) whenever I call startService(intent)?

Stack trace

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{4ab4323 u0 {app package name}/{library package name}.player.PlayerService}
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2240)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loop (Looper.java:246)
android.app.ActivityThread.main (ActivityThread.java:8506)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:602)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1130)

I start the service using context.startService(intent) and the service is started in OnResume of the application and the context is the application context.

Also here is how the service is declared in the manifest

 <service
            android:name=".player.PlayerService"
            android:exported="false"
            android:foregroundServiceType="mediaPlayback"
            android:stopWithTask="false">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
  </service>

Update: I found the reason where the call to startForegroundService(Intent) is made, I am using the following receiver from android to help handle actions control devices like headphone buttons, so since I converted the app to androidx it started using the new MediaButtonReceiver

 <receiver android:name="androidx.media.session.MediaButtonReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_BUTTON" />
            </intent-filter>
 </receiver>

This is the code that is executed when the Receiver receives an event

@Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null
                || !Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())
                || !intent.hasExtra(Intent.EXTRA_KEY_EVENT)) {
            Log.d(TAG, "Ignore unsupported intent: " + intent);
            return;
        }
        ComponentName mediaButtonServiceComponentName =
                getServiceComponentByAction(context, Intent.ACTION_MEDIA_BUTTON);
        if (mediaButtonServiceComponentName != null) {
            intent.setComponent(mediaButtonServiceComponentName);
            startForegroundService(context, intent);
            return;
        }
        ComponentName mediaBrowserServiceComponentName = getServiceComponentByAction(context,
                MediaBrowserServiceCompat.SERVICE_INTERFACE);
        if (mediaBrowserServiceComponentName != null) {
            PendingResult pendingResult = goAsync();
            Context applicationContext = context.getApplicationContext();
            MediaButtonConnectionCallback connectionCallback =
                    new MediaButtonConnectionCallback(applicationContext, intent, pendingResult);
            MediaBrowserCompat mediaBrowser = new MediaBrowserCompat(applicationContext,
                    mediaBrowserServiceComponentName, connectionCallback, null);
            connectionCallback.setMediaBrowser(mediaBrowser);
            mediaBrowser.connect();
            return;
        }
        throw new IllegalStateException("Could not find any Service that handles "
                + Intent.ACTION_MEDIA_BUTTON + " or implements a media browser service.");
    }

You can see that it actually starts a foreground service, I am still investigating the crash but at least I know that a foreground service is started in the app.

Also, this crash does not only happen in Samsung like I thought, crashlytics reporting grouped Samsung crashed together because its a crash from the android platform, other instances of the same crash happens less frequently so they were way down in the crash list on firebase.

Upvotes: 11

Views: 2222

Answers (2)

Anatolii
Anatolii

Reputation: 14660

Update

Possible reason

After you provided more code, it's still unclear if you call startForeground() in your PlayerService or not. It looks like you don't, and that's why you're seeing the crash. On the other hand, if this crash is only happening on Android 11, then you may also have a different problem.

Sample code

I just made a simple app to reproduce a crash for PlayerService if startForeground() is not called. Maybe it will be helpful to you.

app/build.gradle

Add a dependency to use the androidx MediaButtonReceiver

...
implementation "androidx.media:media:1.2.1" 
...

AndroidManifest.xml

...
    <service
        android:name=".PlayerService"
        android:exported="false"
        android:foregroundServiceType="mediaPlayback"
        android:stopWithTask="false">
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
        </intent-filter>
    </service>

    <receiver android:name="androidx.media.session.MediaButtonReceiver">
        <intent-filter>
            <action android:name="android.intent.action.MEDIA_BUTTON" />
        </intent-filter>
    </receiver>   
...

MainActivity.java

Emulate the media button event as below

...
@Override
protected void onResume() {
    super.onResume();

    Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
    intent.setClass(this, MediaButtonReceiver.class);
    intent.putExtra(Intent.EXTRA_KEY_EVENT, "test");
    sendBroadcast(intent);
}
... 

PlayerService.java

public class PlayerService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        MediaButtonReceiver.handleIntent(null, intent);

        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

Fix

By adding startForeground() to onCreate() the problem gets fixed. Please note that the way I prepare parameters and use the method is a draft only. For detailed information, please check the official documentation here.

...
    @Override
    public void onCreate() {
        super.onCreate();

        Notification testNotification = new Notification();
        int testId = 1;
        startForeground(testId, testNotification);
    }
...

Upvotes: 3

Mahdi Noori
Mahdi Noori

Reputation: 41

for some foreground services that need to do works that need to access location, microphone, mediaProjection, phoneCall, camera, dataSync, connectedDevice and mediaPlayback, you must specify "foregroundServiceType" in manifest and set ServiceInfo for service when calling startForeground in service. I think this solves your problem.

Edit

You should start your service like this in your onStartCommand of your service:

if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q){
            startForeground(NOTIFICATION_ID, notification)
        }
        else{
            startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
        }

are you do it?

Upvotes: -2

Related Questions