Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incoming call no longer brings app to foreground (Android 10) #136

Open
TheRohitSharma opened this issue Dec 9, 2019 · 10 comments
Open

Comments

@TheRohitSharma
Copy link

TheRohitSharma commented Dec 9, 2019

It appears Android 10 is being difficult when it comes to launching the app on an incoming call. Everything worked fine on Android <10. The only way I've been able to get my app to launch on an incoming call, is by setting Display over other apps to true, but I don't think that's correct. I've looked through these:
https://developer.android.com/guide/components/activities/background-starts
twilio/voice-quickstart-android#310

But it's going very slow as I don't have much native experience at all. Any help is appreciated.

Did you follow all the instructions as specified in the Twilio Quickstart repositories? Yes
What version of React Native are you running? 0.61.2
What version of react-native-twilio-programmable-voice are you running? 3.21.3
What device are you using? (e.g iOS10 simulator, Android 7 device)? Pixel with Android 10
Is your app running in foreground, background or not running? Foreground and background
Is there any relevant message in the log? Not that I can see

Step to reproduce

Advanced:
Have you tried adding break point using AndroidStudio or XCode and analyse the logs? No
can share a project with issue? No

@TheRohitSharma TheRohitSharma changed the title Incoming call no longer brings app to foreground Incoming call no longer brings app to foreground (Android) Dec 9, 2019
@TheRohitSharma TheRohitSharma changed the title Incoming call no longer brings app to foreground (Android) Incoming call no longer brings app to foreground (Android 10) Dec 10, 2019
@fabriziomoscon
Copy link
Collaborator

fabriziomoscon commented Dec 11, 2019

Hi,

for sure 3.21.3 is not compatible with RN 0.61.
If you checkout PRs you will notice that we are coordinating among open source contributors to fix this.
You can contribute by testing:
yarn add https://github.com/hoxfon/react-native-twilio-programmable-voice#feat/v4
and report whether that changes anything.

Thank you

@aniravi24
Copy link
Contributor

aniravi24 commented Jan 5, 2020

This is likely due to the new restrictions on Android 10 for starting activities from the background. We'll need to figure out if we can meet one of their guidelines and allow this library to open the app. https://developer.android.com/guide/components/activities/background-starts

In one of my projects, we've currently been using react-native-callkeep to handle the call UI. https://github.com/react-native-webrtc/react-native-callkeep

@jdegger
Copy link
Collaborator

jdegger commented Apr 22, 2020

@aniravi24 does react native callkeep work around the issue? It doesn't become very clear for me after reading their stuff.

@aniravi24
Copy link
Contributor

@jdegger since react-native-callkeep uses the native dialer APIs, I managed to get it to open in all cases (foreground, background, app killed, screen off, etc.). However, to make this work in the background, I wrote a little patch for this library (specific to our app only) to send a custom broadcast to my Android app when a call comes in, which then would pass the data along from a BroadcastReceiver to the React Native headless JS task, where I could finally call react-native-callkeep from the JavaScript layer.

@jdegger
Copy link
Collaborator

jdegger commented Apr 22, 2020

@aniravi24 thanks for the quick response. I just read your message in the open V3 PR where you explained it.

I understand that your code is specific your app only, are there more concrete pointers you can give us? Callkeep looks nice. Might it be interesting to PR this behavior in to this repository @fabriziomoscon?

@aniravi24
Copy link
Contributor

aniravi24 commented Apr 22, 2020

@jdegger sure! I'll use some code examples from our work. In VoiceFirebaseMessagingService.java within this library, we have code inside the onCallInvite and onCancelledCallInvite method that looks like this. Make sure to put something like this in the places where an incoming call can come through. We also commented out a few lines for showing notifications as the native dialer will handle all the notifications except for missed calls (I believe missed calls were the only ones I used this library for, I think the native dialer used to work for missed calls too, but couldn't get them to work correctly recently).

Intent callKeepIntent = new Intent();
callKeepIntent.setAction("com.buzzwordlabs.pepper.BACKGROUND_CALL");
callKeepIntent.setPackage("com.buzzwordlabs.pepper");
Bundle bundle = new Bundle();
bundle.putString("callState", "PENDING"); // or "CANCELLED"
bundle.putString("callSid", callInvite.getCallSid());
bundle.putString("handle", callInvite.getFrom());
callKeepIntent.putExtras(bundle);
sendBroadcast(callKeepIntent);

Basically what I'm doing is that I'm having this library fire an intent to code that my app can receive.

The rest of the instructions are in your own app, not in this library.

In AndroidManifest.xml, we have something like this.

<service android:name=".BackgroundCallTaskService"/>
        <receiver android:name=".BackgroundCallReceiver">
            <intent-filter>
                <action android:name="com.buzzwordlabs.pepper.BACKGROUND_CALL" />
            </intent-filter>
        </receiver>

This registers an intent filter so your app can listen for that intent I'm firing in the first part.

We have a file at the same level as MainActivity.java on the Android side that is a BroadcastReceiver like this.

package com.buzzwordlabs.pepper;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.facebook.react.HeadlessJsTaskService;

import java.util.List;

public class BackgroundCallReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(final Context context, final Intent intent) {
        /**
         This part will be called everytime network connection is changed
         e.g. Connected -> Not Connected
         **/
        if (!isAppOnForeground((context))) {
            /**
             We will start our service and send extra info about
             network connections
             **/
            Bundle extras = intent.getExtras();
            Intent serviceIntent = new Intent(context, BackgroundCallTaskService.class);
            serviceIntent.putExtras(extras);
            context.startService(serviceIntent);
            HeadlessJsTaskService.acquireWakeLockNow(context);
        }
    }

    private boolean isAppOnForeground(Context context) {
        /**
         We need to check if app is in foreground otherwise the app will crash.
         http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
         **/
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses =
                activityManager.getRunningAppProcesses();
        if (appProcesses == null) {
            return false;
        }
        final String packageName = context.getPackageName();
        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance ==
                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
                    appProcess.processName.equals(packageName)) {
                return true;
            }
        }
        return false;
    }


}

This broadcast receiver figures out what to do with that intent I just called it with. In this case, it's passing the data along to the headless task only when the app is in the background (because we have code with react native callkeep and this library at the JS layer that can be reached without this service when the app is in the foreground).

And then, a react native headless service as mentioned in the react native docs that looks like this.

package com.buzzwordlabs.pepper;

import android.content.Intent;
import android.os.Bundle;

import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;

public class BackgroundCallTaskService extends HeadlessJsTaskService {
    @Override
    protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras != null) {
            return new HeadlessJsTaskConfig(
                    "BackgroundCallTaskService",
                    Arguments.fromBundle(extras),
                    5000, // timeout for the task
                    false // optional: defines whether or not  the task is allowed in foreground. Default is false
            );
        }
        return null;
    }
}

This receives that information from the BroadcastReceiver and passes it to the JS layer.

And then finally, in index.js (or ts, this example is TypeScript)

if (Platform.OS === 'android') {
  AppRegistry.registerHeadlessTask(
    'BackgroundCallTaskService',
    () => ({
      callState,
      callSid,
      handle,
    }: {
      callState: string;
      callSid: string;
      handle: string; // this is the phone number or contact string from twilio
    }) => {
      if (callState === 'PENDING') {
        // Initialise RNCallKeep
        const options = RNCallKeepOptions; // just an object we defined in another file with the standard callkeep config
        RNCallKeep.setup(options);
        RNCallKeep.setAvailable(true);

        removeEventListeners();

        addEventListeners();
        RNCallKeep.displayIncomingCall(callSid, handle);
      } else {
        removeEventListeners();
      }
      return Promise.resolve();
    },
  );
}

Here, you can see that I'm using the call state to figure out what to do. If the call is gone, we don't want to keep showing the UI.

This should help explain the flow, but of course you'll need to customize it for your needs. Hope this helps! Do note that to manage callkeep and this library on Android and iOS for both the foreground and background, there's a fair bit of event listener management. If something weird is going on, make to sure to check your event listeners are only being fired the number of times you want them to fire (should be once usually).

@jdegger
Copy link
Collaborator

jdegger commented Apr 23, 2020

@aniravi24 many many thanks for the throughout description of how you tackled this issue. This information will be invaluable to our use case. At the moment I'm researching the possibilities and we will start development within a month. Thanks again.

@jdegger
Copy link
Collaborator

jdegger commented Oct 27, 2020

We have planned to fix this with the highest priority, check #158 for an update.

@fabriziomoscon
Copy link
Collaborator

fabriziomoscon commented Nov 13, 2020

@jdegger I followed this approach and submitted my results so far in #163 .
I still can't open the RN app when receiving the call. It might not be possible. This PR doesn't use CallKeep, since I wanted to understand the limitation without extra libraries.

I found that the broadcast event works only if the correct Android app package name is passed, this means that the library must find the host app package dynamically. However, differently from @aniravi24's instructions, it is possible to add the broadcastReceiver and the HeadlessService java files in the library.

@jdegger
Copy link
Collaborator

jdegger commented Nov 13, 2020

@fabriziomoscon that's great. Starting the app won't indeed be possible. The rest looks great so far 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants