datso / react-native-pjsip

A PJSIP module for React Native.
http://datso.github.io/react-native-pjsip
GNU General Public License v3.0
271 stars 227 forks source link

[Android 9+] Microphone audio stream ends after 1 min of talk in background #206

Open DrSchmurge opened 4 years ago

DrSchmurge commented 4 years ago

On Android 9+, after going to background mode, when being in active call, the stream from microphone is being cut after 1 minute of talking. When moving back to foreground, the stream becomes active again. My research showed that Android 9+ has this behavior for apps for security reasons. https://developer.android.com/guide/topics/media/mediarecorder

Note: On devices running Android 9 (API level 28) or higher, apps running in the background cannot access the microphone. Therefore, your app should record audio only when it's in the foreground or when you include an instance of MediaRecorder in a foreground service.

To prevent this, a FOREGROUND SERVICE must be started for a process, that uses a microphone (with static ongoing notification like "this app is using microphone in background. Google Meets, Hangouts and WhatsApp have the same behavior). Have anybody tried this ? How can pjsib be started inside a foreground service? I am new to Java and android architecture in general, so any help will be highly appreciated Many thanks in advance.

elburu commented 3 years ago

Hi @DrSchmurge , did you solve this issue?

DrSchmurge commented 3 years ago

Hi @DrSchmurge , did you solve this issue?

Hi, yes, I added a startForeground() to PjSipService.java, and it works fine. I found no workarounds

elburu commented 3 years ago

Thanks, do you have any snippet please, I never used Android/java before

DrSchmurge commented 3 years ago

Thanks, do you have any snippet please, I never used Android/java before

yes, sure, I added this to PjsipService.java

    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Pjsip", importance);
            channel.setDescription("CHANEL DESCRIPTION bla bla bla");
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }

    private Intent buildForegroundIntent() {
        String foregroundIntentName = getApplicationContext().getPackageName() + ".MainActivity";
        Class<?> foregroundIntentCls = null;

        try {
            foregroundIntentCls = Class.forName(foregroundIntentName);
        } catch (ClassNotFoundException e) {
            Log.e(TAG, "Could not found main activity class, please check whether \""+ foregroundIntentName +"\" available", e);
            throw new RuntimeException(e);
        }

        Intent foregroundIntent = new Intent(this, foregroundIntentCls);
        foregroundIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

        return foregroundIntent;
    }

    private Notification createNotification(String contentTitle, String contentText ) {

       Intent notificationIntent = buildForegroundIntent();
       PendingIntent pendingIntent = PendingIntent.getActivity(this, SERVICE_NOTIFICATION_ID, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

       Notification.Builder notification;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel();
            notification = new Notification.Builder(this, CHANNEL_ID).setContentTitle(contentTitle).setContentText(contentText)
                               .setOngoing(true).setSound(null).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.raw.ic_launcher_hd)).setSmallIcon(R.raw.ic_launcher_smallicon).setContentIntent(pendingIntent);
        } else {
            notification = new Notification.Builder(this).setContentTitle(contentTitle).setContentText(contentText).setOngoing(true)
                               .setSound(null).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.raw.ic_launcher_hd)).setSmallIcon(R.raw.ic_launcher_smallicon).setContentIntent(pendingIntent);
        }

        return notification.build();
    }

    private void updateNotification(String contentTitle, String contentText) {

        Notification newNotification = createNotification(contentTitle, contentText);

        NotificationManager mNotificationManager = getSystemService(NotificationManager.class);
        mNotificationManager.notify(SERVICE_NOTIFICATION_ID, newNotification);
    }

    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
       if (intent != null && intent.getAction() != null && (intent.getAction() == "start" || intent.getAction() == "change_notification")) {
              if (intent != null && intent.hasExtra("notificationContentTitle")) {
                 // Log.d("SCHMURGE", "inside if 1");
                  mContentTitle = intent.getStringExtra("notificationContentTitle");
              }

              if (intent != null && intent.hasExtra("notificationContentText")) {
                 // Log.d("SCHMURGE", "inside if 2");
                  mContentText = intent.getStringExtra("notificationContentText");
              }

              if (intent.getAction() == "start") {
                startForeground(SERVICE_NOTIFICATION_ID, createNotification(mContentTitle, mContentText));
              }
              else {
                updateNotification(mContentTitle, mContentText);
              }
            }
elburu commented 3 years ago

@DrSchmurge very useful, thank you!