Skip to content

Commit

Permalink
Merge pull request #440 from react-native-webrtc/display_incoming_cal…
Browse files Browse the repository at this point in the history
…l_natively_android

Allow to display incoming call natively on Android
  • Loading branch information
manuquentin authored Oct 13, 2021
2 parents 0b891fd + a778b96 commit 559933a
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 30 deletions.
13 changes: 11 additions & 2 deletions actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,17 @@ const didActivateAudioSession = handler =>
const didDeactivateAudioSession = handler =>
eventEmitter.addListener(RNCallKeepDidDeactivateAudioSession, handler);

const didDisplayIncomingCall = handler =>
eventEmitter.addListener(RNCallKeepDidDisplayIncomingCall, (data) => handler(data));
const didDisplayIncomingCall = handler => eventEmitter.addListener(RNCallKeepDidDisplayIncomingCall, data => {
// On Android the payload parameter is sent a String
// As it requires too much code on Android to convert it to WritableMap, let's do it here.
if (data.payload && typeof data.payload === 'string') {
try {
data.payload = JSON.parse(data.payload);
} catch (_) {
}
}
handler(data);
});

const didPerformSetMutedCallAction = handler =>
eventEmitter.addListener(RNCallKeepDidPerformSetMutedCallAction, (data) => handler(data));
Expand Down
134 changes: 107 additions & 27 deletions android/src/main/java/io/wazo/callkeep/RNCallKeepModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter;
import com.facebook.react.modules.permissions.PermissionsModule;
Expand Down Expand Up @@ -95,6 +96,8 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
public static final int REQUEST_READ_PHONE_STATE = 1337;
public static final int REQUEST_REGISTER_CALL_PROVIDER = 394859;

public static RNCallKeepModule instance = null;

private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
private static final String REACT_NATIVE_MODULE_NAME = "RNCallKeep";
private static String[] permissions = {
Expand All @@ -112,18 +115,32 @@ public class RNCallKeepModule extends ReactContextBaseJavaModule {
private boolean isReceiverRegistered = false;
private VoiceBroadcastReceiver voiceBroadcastReceiver;
private ReadableMap _settings;
private WritableNativeArray delayedEvents;
private boolean hasListeners = false;

public static RNCallKeepModule getInstance(ReactApplicationContext reactContext, boolean realContext) {
if (instance == null) {
instance = new RNCallKeepModule(reactContext);
}
if (realContext) {
instance.setContext(reactContext);
}
return instance;
}

public RNCallKeepModule(ReactApplicationContext reactContext) {
private RNCallKeepModule(ReactApplicationContext reactContext) {
super(reactContext);
Log.d(TAG, "[VoiceConnection] constructor");

this.reactContext = reactContext;
delayedEvents = new WritableNativeArray();
this.registerReceiver();
}

private boolean isSelfManaged() {
try {
try {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && _settings.hasKey("selfManaged") && _settings.getBoolean("selfManaged");
} catch(Exception e) {
} catch (Exception e) {
return false;
}
}
Expand All @@ -133,12 +150,56 @@ public String getName() {
return REACT_NATIVE_MODULE_NAME;
}

public void setContext(ReactApplicationContext reactContext) {
Log.d(TAG, "[VoiceConnection] updating react context");
this.reactContext = reactContext;
}

public void reportNewIncomingCall(String uuid, String number, String callerName, boolean hasVideo, String payload) {
Log.d(TAG, "[VoiceConnection] reportNewIncomingCall, uuid: " + uuid + ", number: " + number + ", callerName: " + callerName);
// @TODO: handle video

this.displayIncomingCall(uuid, number, callerName);

// Send event to JS
WritableMap args = Arguments.createMap();
args.putString("handle", number);
args.putString("callUUID", uuid);
args.putString("name", callerName);
if (payload != null) {
args.putString("payload", payload);
}
sendEventToJS("RNCallKeepDidDisplayIncomingCall", args);
}

public void startObserving() {
int count = delayedEvents.size();
Log.d(TAG, "[VoiceConnection] startObserving, event count: " + count);
if (count > 0) {
this.reactContext.getJSModule(RCTDeviceEventEmitter.class).emit("RNCallKeepDidLoadWithEvents", delayedEvents);
delayedEvents = new WritableNativeArray();
}
}

public void initializeTelecomManager() {
Context context = this.getAppContext();
ComponentName cName = new ComponentName(context, VoiceConnectionService.class);
String appName = this.getApplicationName(context);

handle = new PhoneAccountHandle(cName, appName);
telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
}

public void setSettings(ReadableMap options) {
this._settings = options;
}

@ReactMethod
public void setup(ReadableMap options) {
Log.d(TAG, "[VoiceConnection] setup");
VoiceConnectionService.setAvailable(false);
VoiceConnectionService.setInitialized(true);
this._settings = options;
this.setSettings(options);

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (isSelfManaged()) {
Expand All @@ -158,6 +219,7 @@ public void setup(ReadableMap options) {
if (isConnectionServiceAvailable()) {
this.registerPhoneAccount(options);
this.registerEvents();
this.startObserving();
VoiceConnectionService.setAvailable(true);
}

Expand Down Expand Up @@ -187,11 +249,18 @@ public void registerEvents() {

Log.d(TAG, "[VoiceConnection] registerEvents");

voiceBroadcastReceiver = new VoiceBroadcastReceiver();
registerReceiver();
this.hasListeners = true;
this.startObserving();
VoiceConnectionService.setPhoneAccountHandle(handle);
}

@ReactMethod
public void unregisterEvents() {
Log.d(TAG, "[RNCallKeepModule] unregisterEvents");

this.hasListeners = false;
}

@ReactMethod
public void displayIncomingCall(String uuid, String number, String callerName) {
if (!isConnectionServiceAvailable() || !hasPhoneAccount()) {
Expand Down Expand Up @@ -410,6 +479,11 @@ public void checkDefaultPhoneAccount(Promise promise) {
promise.resolve(!hasSim || hasDefaultAccount);
}

@ReactMethod
public void getInitialEvents(Promise promise) {
promise.resolve(delayedEvents);
}

@ReactMethod
public void setOnHold(String uuid, boolean shouldHold) {
Log.d(TAG, "[VoiceConnection] setOnHold, uuid: " + uuid + ", shouldHold: " + (shouldHold ? "true" : "false"));
Expand Down Expand Up @@ -721,13 +795,21 @@ public void backToForeground() {
}
}

private void initializeTelecomManager() {
Context context = this.getAppContext();
ComponentName cName = new ComponentName(context, VoiceConnectionService.class);
String appName = this.getApplicationName(context);
public static void onRequestPermissionsResult(int requestCode, String[] grantedPermissions, int[] grantResults) {
int permissionsIndex = 0;
List<String> permsList = Arrays.asList(permissions);
for (int result : grantResults) {
if (permsList.contains(grantedPermissions[permissionsIndex]) && result != PackageManager.PERMISSION_GRANTED) {
hasPhoneAccountPromise.resolve(false);
return;
}
permissionsIndex++;
}
hasPhoneAccountPromise.resolve(true);
}

handle = new PhoneAccountHandle(cName, appName);
telecomManager = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
private boolean isSelfManaged() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && _settings.hasKey("selfManaged") && _settings.getBoolean("selfManaged");
}

private void registerPhoneAccount(Context appContext) {
Expand Down Expand Up @@ -761,8 +843,18 @@ private void registerPhoneAccount(Context appContext) {
}

private void sendEventToJS(String eventName, @Nullable WritableMap params) {
Log.v(TAG, "[VoiceConnection] sendEventToJS, eventName :" + eventName + ", args : " + (params != null ? params.toString() : "null"));
this.reactContext.getJSModule(RCTDeviceEventEmitter.class).emit(eventName, params);
boolean isBoundToJS = this.reactContext.hasActiveCatalystInstance();
Log.v(TAG, "[VoiceConnection] sendEventToJS, eventName: " + eventName + ", bound: " + isBoundToJS + ", hasListeners: " + hasListeners + " args : " + (params != null ? params.toString() : "null"));

if (isBoundToJS && hasListeners) {
this.reactContext.getJSModule(RCTDeviceEventEmitter.class).emit(eventName, params);
} else {
if (params == null) {
params = Arguments.createMap();
}
params.putString("name", eventName);
delayedEvents.pushMap(params);
}
}

private String getApplicationName(Context appContext) {
Expand Down Expand Up @@ -797,6 +889,7 @@ private static boolean hasPhoneAccount() {

private void registerReceiver() {
if (!isReceiverRegistered) {
voiceBroadcastReceiver = new VoiceBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_END_CALL);
intentFilter.addAction(ACTION_ANSWER_CALL);
Expand All @@ -820,19 +913,6 @@ private Context getAppContext() {
return this.reactContext.getApplicationContext();
}

public static void onRequestPermissionsResult(int requestCode, String[] grantedPermissions, int[] grantResults) {
int permissionsIndex = 0;
List<String> permsList = Arrays.asList(permissions);
for (int result : grantResults) {
if (permsList.contains(grantedPermissions[permissionsIndex]) && result != PackageManager.PERMISSION_GRANTED) {
hasPhoneAccountPromise.resolve(false);
return;
}
permissionsIndex++;
}
hasPhoneAccountPromise.resolve(true);
}

private class VoiceBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class RNCallKeepPackage implements ReactPackage {

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new RNCallKeepModule(reactContext));
return Collections.<NativeModule>singletonList(RNCallKeepModule.getInstance(reactContext, true));
}

// Deprecated RN 0.47
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ declare module 'react-native-callkeep' {

static registerAndroidEvents(): void

static unregisterAndroidEvents(): void

static displayIncomingCall(
uuid: string,
handle: string,
Expand Down
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ class RNCallKeep {
RNCallKeepModule.registerEvents();
};

unregisterAndroidEvents = () => {
if (isIOS) {
return;
}
RNCallKeepModule.unregisterEvents();
};

hasDefaultPhoneAccount = async (options) => {
if (!isIOS) {
return this._hasDefaultPhoneAccount(options);
Expand Down

0 comments on commit 559933a

Please sign in to comment.