react-native-webrtc / react-native-callkeep

iOS CallKit framework and Android ConnectionService for React Native
ISC License
922 stars 445 forks source link

Question: Android - How to update call when app is in background #119

Closed danwhite-ipc closed 4 years ago

danwhite-ipc commented 5 years ago

Question

Description

I would like to understand what mechanisms others have used to update a call on android while the app is in the background. I require to notify CallKeep that the remote party has answered or ended the call.

Steps to Reproduce

Outbound Call started (Native UI launched and app backgrounded) Remote party answers the call - Need to notify CallKeep that the answer has happened to call setCurrentCallActive

Versions

- Callkeep: 3.0.6
- React Native: 0.59.10
- Android: 8.1.0
- Phone model: Samsung J4+
danjenkins commented 5 years ago

@danwhite-ad I don't quite understand - your app is backgrounded but still running and so all of your logic still works from your react native app. When the call is answered, from your app call the relevant callkeep API to say that its been answered etc

danwhite-ipc commented 5 years ago

My app uses redux to provide the application with the list of calls and an example of my question is... When the remote party hangs up, redux removes the call from my collection but because the app is in the background during a call (on Android) the redux code is not processed so I am unable to notify the connection service that the remove party has ended the call.

garronej commented 4 years ago

I found a workaround: I start a Headless JS task when the call begins, the task resolve when the call ends. This way the timers callbacks keep being executed when the native UI is in the foreground.

Daneng66 commented 4 years ago

@garronej Could you post an example?

garronej commented 4 years ago

Sure, It involves a stupid amount of boiler plate for such a simple task though. And note that I opted for an implementation slightly different from the one I describe in my previous comment.

android/app/src/main/java/[your_path]/EndlessPhonyTaskService.java

package com.semasim.semasim;

import android.content.Intent;

import androidx.annotation.Nullable;

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

public class EndlessPhonyTaskService extends HeadlessJsTaskService {

    @Override
    protected @Nullable
    HeadlessJsTaskConfig getTaskConfig(Intent intent) {

        return new HeadlessJsTaskConfig(
                "EndlessPhonyTask",
                Arguments.createMap(),
                0,
                true
        );
    }

}

Put in your AndroidManifest.xml:

      <service android:name="com.semasim.semasim.EndlessPhonyTaskService" />

in your index.android.js add:

AppRegistry.registerHeadlessTask("EndlessPhonyTask", () => () => new Promise(() => { }));

see https://facebook.github.io/react-native/docs/headless-js-android

android/app/src/main/java/[your_path]/HostKeepAlive.java

package com.semasim.semasim;

import android.content.Intent;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class HostKeepAlive extends ReactContextBaseJavaModule {

    private ReactApplicationContext reactContext;

    HostKeepAlive(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

    @Override
    public String getName() {
        return "HostKeepAlive";
    }

    private boolean isKeptAlive= false;

    @ReactMethod
    public void start( ){

        if( isKeptAlive ) {
            return;
        }

        isKeptAlive= true;

        reactContext.startService(new Intent(reactContext, EndlessPhonyTaskService.class));

    }

    @ReactMethod
    public void stop(){

        if( !isKeptAlive ) {
            return;
        }

        isKeptAlive=false;

        reactContext.stopService(new Intent(reactContext, EndlessPhonyTaskService.class));

    }

}

android/app/src/main/java/[your_path]/HostKeepAlivePackage.java

package com.semasim.semasim;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class HostKeepAlivePackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext ) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new HostKeepAlive(reactContext));

        return modules;
    }

}

Eddit your android/app/src/main/java/[your_path]/MailApplication.java getPackages() method so you add the new native module ( see: https://facebook.github.io/react-native/docs/native-modules-android )

[...]
    @Override
    protected List<ReactPackage> getPackages() {

      List<ReactPackage> packages = new PackageList(this).getPackages();
      // Packages that cannot be autolinked yet can be added manually here

      [...]
      packages.add(new HostKeepAlivePackage());

      return packages;
    }

[...]

Then in your code you should call HostKeepAlive.start() when a call start and HostKeepAlive.stop() when it's ended so whenever the native UI is in the foreground the app be kept alive by the Headless JS task:

import * as rn from "react-native"
const { HostKeepAlive } = rn.NativeModules;
import RNCallKeep from "react-native-callkeep";

RNCallKeep.addEventListener("endCall", () => HostKeepAlive.stop());

[...]
//Call start
RNCallKeep.startCall([...]);

HostKeepAlive.start();

[...]
//When the outgoing call is not terminated by the user:
RNCallKeep.reportEndCallWithUUID([...]);
HostKeepAlive.stop();

//Don't forget to do it as well for incoming calls...
manuquentin commented 4 years ago

Hi @danwhite-ad, On Android, we've opted to use push every times the user receive a call. If the app is killed or in background, it will start in headless mode (thanks to react-native-firebase) and if it's in background.

We choose this because after a certain amount a time in Background (10/15mn), Android will close all Websocket connection.