flutter_foreground_task icon indicating copy to clipboard operation
flutter_foreground_task copied to clipboard

Foreground service type missing in to the new version 6.4.0

Open anandStratBeans opened this issue 1 year ago • 12 comments

The foreground service type is missing in the new version 6.4.0. Due to this the Android app crashes continuously. FATAL EXCEPTION: main Process: com.stratbeans.bytecastingevolve, PID: 13153 java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=333, result=-1, data=Intent { (has extras) }} to activity {com.stratbeans.bytecastingevolve/com.stratbeans.bytecastingevolve.MainActivity}: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION at android.app.ActivityThread.deliverResults(ActivityThread.java:5527) at android.app.ActivityThread.handleSendResult(ActivityThread.java:5566) at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:67) at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443) 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:8177) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971) Caused by: java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION 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.media.projection.IMediaProjection$Stub$Proxy.start(IMediaProjection.java:313) at android.media.projection.MediaProjection.(MediaProjection.java:84) at android.media.projection.MediaProjection.(MediaProjection.java:75) at android.media.projection.MediaProjectionManager.getMediaProjection(MediaProjectionManager.java:236) at com.isvisoft.flutter_screen_recording.FlutterScreenRecordingPlugin.onActivityResult(FlutterScreenRecordingPlugin.kt:53) at io.flutter.embedding.engine.FlutterEngineConnectionRegistry$FlutterEngineActivityPluginBinding.onActivityResult(FlutterEngineConnectionRegistry.java:774) at io.flutter.embedding.engine.FlutterEngineConnectionRegistry.onActivityResult(FlutterEngineConnectionRegistry.java:422) at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onActivityResult(FlutterActivityAndFragmentDelegate.java:857) at io.flutter.embedding.android.FlutterActivity.onActivityResult(FlutterActivity.java:884) at android.app.Activity.dispatchActivityResult(Activity.java:8943) at android.app.ActivityThread.deliverResults(ActivityThread.java:5520) at android.app.ActivityThread.handleSendResult(ActivityThread.java:5566)  at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:67)  at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2443)  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:8177)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)  Caused by: android.os.RemoteException: Remote stack trace: at com.android.server.media.projection.MediaProjectionManagerService$MediaProjection.start(MediaProjectionManagerService.java:940) at android.media.projection.IMediaProjection$Stub.onTransact(IMediaProjection.java:192) at android.os.Binder.execTransactInternal(Binder.java:1339) at android.os.Binder.execTransact(Binder.java:1275)

anandStratBeans avatar Jun 04 '24 12:06 anandStratBeans

I got this error when trying both version 6.3.0 and 6.4.0. Is it related to this package's error?

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to create service com.pravera.flutter_foreground_task.service.ForegroundService: java.lang.SecurityException: Starting FGS with type microphone callerApp=ProcessRecord{64b48c9 18748:/u0a185} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_MICROPHONE] any of the permissions allOf=false [android.permission.CAPTURE_AUDIO_HOTWORD, android.permission.CAPTURE_AUDIO_OUTPUT, android.permission.CAPTURE_MEDIA_OUTPUT, android.permission.CAPTURE_TUNER_AUDIO_INPUT, android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT, android.permission.RECORD_AUDIO]  and the app must be in the eligible state/exemptions to access the foreground only permission
	at android.app.ActivityThread.handleCreateService(ActivityThread.java:4663)
	at android.app.ActivityThread.-$$Nest$mhandleCreateService(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2264)
	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:8176)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: java.lang.SecurityException: Starting FGS with type microphone callerApp=ProcessRecord{64b48c9 18748:/u0a185} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_MICROPHONE] any of the permissions allOf=false [android.permission.CAPTURE_AUDIO_HOTWORD, android.permission.CAPTURE_AUDIO_OUTPUT, android.permission.CAPTURE_MEDIA_OUTPUT, android.permission.CAPTURE_TUNER_AUDIO_INPUT, android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT, android.permission.RECORD_AUDIO]  and the app must be in the eligible state/exemptions to access the foreground only permission
	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:6745)
	at android.app.Service.startForeground(Service.java:862)
	at com.pravera.flutter_foreground_task.service.ForegroundService.startForegroundService(ForegroundService.kt:252)
	at com.pravera.flutter_foreground_task.service.ForegroundService.onCreate(ForegroundService.kt:85)
	at android.app.ActivityThread.handleCreateService(ActivityThread.java:4650)
	at android.app.ActivityThread.-$$Nest$mhandleCreateService(Unknown Source:0) 
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2264) 
	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:8176) 
	at java.lang.reflect.Method.invoke(Native Method) 
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) 
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971) 
Caused by: android.os.RemoteException: Remote stack trace:
	at com.android.server.am.ActiveServices.validateForegroundServiceType(ActiveServices.java:2718)
	at com.android.server.am.ActiveServices.setServiceForegroundInnerLocked(ActiveServices.java:2418)
	at com.android.server.am.ActiveServices.setServiceForegroundLocked(ActiveServices.java:1666)
	at com.android.server.am.ActivityManagerService.setServiceForeground(ActivityManagerService.java:13253)
	at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:3378)

In AndroidManifest.xml:

 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<service
            android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
            android:stopWithTask="true"
            android:foregroundServiceType="microphone"
            android:exported="false" />

tatuan19 avatar Jun 05 '24 04:06 tatuan19

Check runtime prerequisites. https://developer.android.com/about/versions/14/changes/fgs-types-required#microphone

image

You must grant RECORD_AUDIO permission before starting the service.

Dev-hwang avatar Jun 05 '24 05:06 Dev-hwang

@Dev-hwang My issue is with the MEDIA_PROJECTION service. How exactly do I grant it before the service starts? It's already specified in the manifest.

Plugin version - 6.4.0

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<service
    android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
    android:foregroundServiceType="mediaPlayback|camera|microphone|mediaProjection"
    android:stopWithTask="true"/>

Error -

java.lang.RuntimeException: Unable to create service com.pravera.flutter_foreground_task.service.ForegroundService: java.lang.SecurityException: Starting FGS with type mediaProjection callerApp=ProcessRecord{91d52c4 15903:com.example.test/u0a206} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION] any of the permissions allOf=false [android.permission.CAPTURE_VIDEO_OUTPUT, android:project_media] 

bhavinb98 avatar Jun 06 '24 12:06 bhavinb98

I am also experience issues on Android regarding the FOREGROUND_SERVICE_MICROPHONE, I downgraded to 6.3 to specify the foregroundServiceTypes. The ForgroundService starts as expected but when I try to start my audio recording with the record dependency I get an error from the record that I lack the permission. As @bhavinb98 and @tatuan19 are using the FOREGROUND_SERVICE_MICROPHONE I wonder how you manage to record audio in the FS.

knma1992 avatar Jul 09 '24 10:07 knma1992

@knma1992 Hmm not sure. My app asks for the access microphone permission during startup, after that there's no other configuration. My foreground service code is pretty much similar to the one in the readme.

bhavinb98 avatar Jul 09 '24 11:07 bhavinb98

@bhavinb98 Mine as well, recording works but not in the FS for some reason, is your app on github?

knma1992 avatar Jul 09 '24 11:07 knma1992

@knma1992 Yes, but can't share it since it's a company's app.

bhavinb98 avatar Jul 09 '24 11:07 bhavinb98

Let me just post my code here, maybe I am doing something obviously wrong.

`

void initForegroundTask() {
  FlutterForegroundTask.init(
    androidNotificationOptions: AndroidNotificationOptions(
      foregroundServiceTypes: [AndroidForegroundServiceType.MICROPHONE],
      channelId: 'foreground_service',
      channelName: 'Foreground Service Notification',
      channelDescription: 'This notification appears when the foreground service is running.',
      channelImportance: NotificationChannelImportance.LOW,
      priority: NotificationPriority.LOW,
      iconData: const NotificationIconData(
        resType: ResourceType.mipmap,
        resPrefix: ResourcePrefix.ic,
        name: 'launcher',
      ),
      buttons: [
        const NotificationButton(id: 'sendButton', text: 'Send'),
        const NotificationButton(id: 'testButton', text: 'Test'),
      ],
    ),
    iosNotificationOptions: const IOSNotificationOptions(
      showNotification: true,
      playSound: false,
    ),
    foregroundTaskOptions: const ForegroundTaskOptions(
      interval: 1000,
      isOnceEvent: false,
      autoRunOnBoot: true,
      allowWakeLock: true,
      allowWifiLock: true,
    ),
  );
}

@override
void initState() {
  initForegroundTask();
}

// The callback function should always be a top-level function.
@pragma('vm:entry-point')
void startCallback() {
  // The setTaskHandler function must be called to handle the task in the background.
  FlutterForegroundTask.setTaskHandler(FirstTaskHandler());
}

enum TaskState { none, running, paused }

class FirstTaskHandler extends TaskHandler {
  SendPort? _sendPort;
  TaskState _taskState = TaskState.none;

  void _startController() async {

    final record = AudioRecorder();

    final inputDevices = await record.listInputDevices();
    final inputDevice = inputDevices[1];
    logger.i(inputDevice);

    if (await record.hasPermission()) {
      logger.i("Permission granted");
    } else {
      logger.e("Permission denied");  //<-This is where it throws permission denied even though it works perfectly fine outside the FS 
      return;
    }

    final recordConfig = RecordConfig(
        encoder: AudioEncoder.pcm16bits,
        sampleRate: 16000,
        numChannels: 1,
        device: inputDevice); //bitRate: 128000

    final stream = await record.startStream(recordConfig);

    stream.listen(
      (data) async {
        print(data);
      },

      onError: (error) {
        logger.e("onError error = ${error.toString()}");
      },

      onDone: () {
        logger.i("onDone");
      },
    ); 

  }

  @override
  void onStart(DateTime timestamp, SendPort? sendPort) async {
    print("onStart");
    _taskState = TaskState.running;
    _sendPort = sendPort;

    final customData = await FlutterForegroundTask.getData<String>(key: 'customData');
    print('customData: $customData');

    _startController();
  }

  @override
  void onRepeatEvent(DateTime timestamp, SendPort? sendPort) async {
    print("onRepeatEvent");
    // Send data to the main isolate.
    sendPort?.send("timestamp: ${timestamp.toString()}");
  }

  @override
  void onDestroy(DateTime timestamp, SendPort? sendPort) async {
    print("onDestroy");
  }

  @override
  void onNotificationButtonPressed(String id) {
    print('onNotificationButtonPressed >> $id');
  }

  @override
  void onNotificationPressed() {
    FlutterForegroundTask.launchApp("/resume-route");
    _sendPort?.send('onNotificationPressed');
  }
}

`

knma1992 avatar Jul 09 '24 11:07 knma1992

@knma1992 Have you tried asking for the record permission outside of the task handler function? or is it the first time it asks for it?

bhavinb98 avatar Jul 09 '24 12:07 bhavinb98

Currently, I grant permission on a separate screen using the permission handler and I only access the screen from which I start my FS when I have granted all my permissions. What is weird is the fact that if I check permission in my task handler with the permission handler it is granted.

final microphonePermission = await Permission.microphone.isGranted;

But record for some reason does not.

knma1992 avatar Jul 09 '24 12:07 knma1992

Hmm that is weird. I don't know any more :(

bhavinb98 avatar Jul 09 '24 12:07 bhavinb98

Looking into the ForegroundService was a mistake, I should have debugged the record plugin way earlier. When the record plugin checks for permission and when it sinks the audio buffer into the EventChannel it does so via an activity variable that won't be set when the record plugin is started from the second dart entry point. I forked the record plugin and changed it accordingly.

This is the old implementation. fun sendRecordChunkEvent(buffer: ByteArray) { activity?.runOnUiThread { eventSink?.success(buffer) } }

My implementation: fun sendRecordChunkEvent(buffer: ByteArray) { uiThreadHandler.post(Runnable { eventSink?.success(buffer) }) }

Currently, I don't change the permission check implementation inside record as I can check for permissions using the permission_handler plugin and I have no idea how to supply the context to the record plugin. Thanks @bhavinb98 for the help.

knma1992 avatar Jul 24 '24 07:07 knma1992