Skip to content

Commit 740023e

Browse files
authored
Fix background call (#25)
* fix background call. * Fix iOS, close #23. * Fix typo. * More changes. * Remove EventChannel. * Change FlutterCallKeep as a singleton. * Add CallKeepPushKitToken event. * Add firebase_messaging to example. * update.
1 parent 9c065ca commit 740023e

File tree

17 files changed

+287
-92
lines changed

17 files changed

+287
-92
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# callkeep
2-
iOS CallKit and Android ConnectionService for Flutter
2+
* iOS CallKit and Android ConnectionService for Flutter
3+
* Support FCM and PushKit

android/src/main/java/io/wazo/callkeep/CallKeepModule.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ public boolean HandleMethodCall(@NonNull MethodCall call, @NonNull Result result
217217
public void setup(ConstraintsMap options) {
218218
VoiceConnectionService.setAvailable(false);
219219
this._settings = options;
220-
221220
if (isConnectionServiceAvailable()) {
222221
this.registerPhoneAccount();
223222
this.registerEvents();
@@ -598,7 +597,7 @@ private void registerPhoneAccount(Context appContext) {
598597
}
599598

600599
private void sendEventToFlutter(String eventName, @Nullable ConstraintsMap params) {
601-
_eventChannel.invokeMethod(eventName, params != null? params.toMap() : null);
600+
_eventChannel.invokeMethod(eventName, params.toMap());
602601
}
603602

604603
private String getApplicationName(Context appContext) {
@@ -735,10 +734,10 @@ public void onReceive(Context context, Intent intent) {
735734
sendEventToFlutter("CallKeepDidReceiveStartCallAction", args);
736735
break;
737736
case ACTION_AUDIO_SESSION:
738-
sendEventToFlutter("CallKeepDidActivateAudioSession", null);
737+
sendEventToFlutter("CallKeepDidActivateAudioSession", args);
739738
break;
740739
case ACTION_CHECK_REACHABILITY:
741-
sendEventToFlutter("CallKeepCheckReachability", null);
740+
sendEventToFlutter("CallKeepCheckReachability", args);
742741
break;
743742
case ACTION_WAKE_APP:
744743
Intent headlessIntent = new Intent(_context, CallKeepBackgroundMessagingService.class);

android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class VoiceConnectionService extends ConnectionService {
5656
private static Boolean isReachable;
5757
private static String notReachableCallUuid;
5858
private static ConnectionRequest currentConnectionRequest;
59-
private static PhoneAccountHandle phoneAccountHandle;
59+
private static PhoneAccountHandle phoneAccountHandle = null;
6060
private static String TAG = "RNCK:VoiceConnectionService";
6161
public static Map<String, VoiceConnection> currentConnections = new HashMap<>();
6262
public static Boolean hasOutgoingCall = false;

example/.metadata

Lines changed: 0 additions & 10 deletions
This file was deleted.

example/android/app/build.gradle

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,15 @@ if (flutterVersionName == null) {
2222
}
2323

2424
apply plugin: 'com.android.application'
25+
apply plugin: 'com.google.gms.google-services'
2526
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
2627

28+
dependencies {
29+
// Add the dependencies for any other desired Firebase products
30+
// https://firebase.google.com/docs/android/setup#available-libraries
31+
implementation 'com.google.firebase:firebase-messaging:20.3.0'
32+
}
33+
2734
android {
2835
compileSdkVersion 28
2936

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
2828
<!-- END OPTIONAL PERMISSIONS -->
2929
<application
30-
android:name="io.flutter.app.FlutterApplication"
30+
android:name=".Application"
3131
android:label="flutter_callkeep_example"
3232
android:icon="@mipmap/ic_launcher">
3333
<activity
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.github.cloudwebrtc.flutter_callkeep_example;
2+
3+
import android.os.Bundle;
4+
5+
import androidx.annotation.CallSuper;
6+
7+
import io.flutter.app.FlutterApplication;
8+
import io.flutter.app.FlutterActivity;
9+
import io.flutter.plugin.common.PluginRegistry;
10+
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
11+
import io.flutter.plugins.GeneratedPluginRegistrant;
12+
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService;
13+
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin;
14+
import com.github.cloudwebrtc.flutter_callkeep.FlutterCallkeepPlugin;
15+
16+
public class Application extends FlutterApplication implements PluginRegistrantCallback {
17+
@Override
18+
public void onCreate() {
19+
super.onCreate();
20+
FlutterFirebaseMessagingService.setPluginRegistrant(this);
21+
}
22+
23+
@Override
24+
public void registerWith(PluginRegistry pluginRegistry) {
25+
FirebaseMessagingPlugin.registerWith(pluginRegistry.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin"));
26+
FlutterCallkeepPlugin.registerWith(pluginRegistry.registrarFor("com.github.cloudwebrtc.flutter_callkeep.FlutterCallkeepPlugin"));
27+
}
28+
}

example/android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ buildscript {
66

77
dependencies {
88
classpath 'com.android.tools.build:gradle:3.5.0'
9+
classpath 'com.google.gms:google-services:4.3.4'
910
}
1011
}
1112

example/ios/Runner/AppDelegate.m

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ @implementation AppDelegate
66

77
- (BOOL)application:(UIApplication *)application
88
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
9-
[GeneratedPluginRegistrant registerWithRegistry:self];
10-
// Override point for customization after application launch.
11-
return [super application:application didFinishLaunchingWithOptions:launchOptions];
9+
[GeneratedPluginRegistrant registerWithRegistry:self];
10+
// Override point for customization after application launch.
11+
return [super application:application didFinishLaunchingWithOptions:launchOptions];
1212
}
1313

14-
- (BOOL)application:(UIApplication *)application
15-
continueUserActivity:(NSUserActivity *)userActivity
16-
restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler {
14+
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler {
1715
return [CallKeep application:application
1816
continueUserActivity:userActivity
1917
restorationHandler:restorationHandler];
20-
2118
}
19+
2220
@end

example/ios/Runner/Info.plist

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
<true/>
2525
<key>UIBackgroundModes</key>
2626
<array>
27-
<string>fetch</string>
2827
<string>remote-notification</string>
2928
<string>voip</string>
3029
</array>

example/lib/main.dart

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,75 @@
11
import 'dart:async';
2+
import 'dart:io';
23

4+
import 'package:firebase_messaging/firebase_messaging.dart';
35
import 'package:flutter/material.dart';
46

57
import 'package:callkeep/callkeep.dart';
68
import 'package:uuid/uuid.dart';
79

10+
/// For fcm background message handler.
11+
final FlutterCallkeep _callKeep = FlutterCallkeep();
12+
bool _callKeepInited = false;
13+
14+
Future<dynamic> myBackgroundMessageHandler(Map<String, dynamic> message) {
15+
print('backgroundMessage: message => ${message.toString()}');
16+
17+
var number = message['data']['body'] as String;
18+
final callUUID = Uuid().v4();
19+
_callKeep.on(CallKeepPerformAnswerCallAction(),
20+
(CallKeepPerformAnswerCallAction event) {
21+
print(
22+
'backgroundMessage: CallKeepPerformAnswerCallAction ${event.callUUID}');
23+
_callKeep.startCall(event.callUUID, number, number);
24+
25+
Timer(const Duration(seconds: 1), () {
26+
print('[setCurrentCallActive] $callUUID, number: $number');
27+
_callKeep.setCurrentCallActive(callUUID);
28+
});
29+
//_callKeep.endCall(event.callUUID);
30+
});
31+
32+
_callKeep.on(CallKeepPerformEndCallAction(),
33+
(CallKeepPerformEndCallAction event) {
34+
print('backgroundMessage: CallKeepPerformEndCallAction ${event.callUUID}');
35+
});
36+
if (!_callKeepInited) {
37+
_callKeep.setup(<String, dynamic>{
38+
'ios': {
39+
'appName': 'CallKeepDemo',
40+
},
41+
'android': {
42+
'alertTitle': 'Permissions required',
43+
'alertDescription':
44+
'This application needs to access your phone accounts',
45+
'cancelButton': 'Cancel',
46+
'okButton': 'ok',
47+
},
48+
});
49+
_callKeepInited = true;
50+
}
51+
52+
print('backgroundMessage: displayIncomingCall ($number)');
53+
_callKeep.displayIncomingCall(callUUID, number);
54+
_callKeep.backToForeground();
55+
/*
56+
57+
if (message.containsKey('data')) {
58+
// Handle data message
59+
final dynamic data = message['data'];
60+
}
61+
62+
if (message.containsKey('notification')) {
63+
// Handle notification message
64+
final dynamic notification = message['notification'];
65+
print('notification => ${notification.toString()}');
66+
}
67+
68+
// Or do other work.
69+
*/
70+
return null;
71+
}
72+
873
void main() {
974
runApp(MyApp());
1075
}
@@ -35,8 +100,17 @@ class Call {
35100
class _MyAppState extends State<HomePage> {
36101
final FlutterCallkeep _callKeep = FlutterCallkeep();
37102
Map<String, Call> calls = {};
38-
39103
String newUUID() => Uuid().v4();
104+
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
105+
106+
void iOS_Permission() {
107+
_firebaseMessaging.requestNotificationPermissions(
108+
IosNotificationSettings(sound: true, badge: true, alert: true));
109+
_firebaseMessaging.onIosSettingsRegistered
110+
.listen((IosNotificationSettings settings) {
111+
print('Settings registered: $settings');
112+
});
113+
}
40114

41115
void removeCall(String callUUID) {
42116
setState(() {
@@ -176,9 +250,23 @@ class _MyAppState extends State<HomePage> {
176250
handleType: 'number', hasVideo: false);
177251
}
178252

253+
void didDisplayIncomingCall(CallKeepDidDisplayIncomingCall event) {
254+
var callUUID = event.callUUID;
255+
var number = event.handle;
256+
print('[displayIncomingCall] $callUUID number: $number');
257+
setState(() {
258+
calls[callUUID] = Call(number);
259+
});
260+
}
261+
262+
void onPushKitToken(CallKeepPushKitToken event) {
263+
print('[onPushKitToken] token => ${event.token}');
264+
}
265+
179266
@override
180267
void initState() {
181268
super.initState();
269+
_callKeep.on(CallKeepDidDisplayIncomingCall(), didDisplayIncomingCall);
182270
_callKeep.on(CallKeepPerformAnswerCallAction(), answerCall);
183271
_callKeep.on(CallKeepDidPerformDTMFAction(), didPerformDTMFAction);
184272
_callKeep.on(
@@ -187,6 +275,7 @@ class _MyAppState extends State<HomePage> {
187275
_callKeep.on(
188276
CallKeepDidPerformSetMutedCallAction(), didPerformSetMutedCallAction);
189277
_callKeep.on(CallKeepPerformEndCallAction(), endCall);
278+
_callKeep.on(CallKeepPushKitToken(), onPushKitToken);
190279

191280
_callKeep.setup(<String, dynamic>{
192281
'ios': {
@@ -200,6 +289,34 @@ class _MyAppState extends State<HomePage> {
200289
'okButton': 'ok',
201290
},
202291
});
292+
293+
if (Platform.isAndroid) {
294+
//if (isIOS) iOS_Permission();
295+
// _firebaseMessaging.requestNotificationPermissions();
296+
297+
_firebaseMessaging.getToken().then((token) {
298+
print('[FCM] token => ' + token);
299+
});
300+
301+
_firebaseMessaging.configure(
302+
onMessage: (Map<String, dynamic> message) async {
303+
print('onMessage: $message');
304+
if (message.containsKey('data')) {
305+
// Handle data message
306+
final dynamic data = message['data'];
307+
var number = data['body'] as String;
308+
await displayIncomingCall(number);
309+
}
310+
},
311+
onBackgroundMessage: myBackgroundMessageHandler,
312+
onLaunch: (Map<String, dynamic> message) async {
313+
print('onLaunch: $message');
314+
},
315+
onResume: (Map<String, dynamic> message) async {
316+
print('onResume: $message');
317+
},
318+
);
319+
}
203320
}
204321

205322
Widget buildCallingWidgets() {

example/pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: flutter_callkeep_example
22
description: Demonstrates how to use the flutter_callkeep plugin.
3+
version: 0.1.1
34

45
# The following line prevents the package from being accidentally published to
56
# pub.dev using `pub publish`. This is preferred for private packages.
@@ -24,6 +25,8 @@ dependencies:
2425
# Use with the CupertinoIcons class for iOS style icons.
2526
cupertino_icons: ^1.0.0
2627
uuid: ^2.0.2
28+
firebase_messaging: ^7.0.0
29+
2730
dev_dependencies:
2831
flutter_test:
2932
sdk: flutter

ios/Classes/CallKeep.h

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,43 @@
1010
#import <UIKit/UIKit.h>
1111
#import <CallKit/CallKit.h>
1212
#import <Intents/Intents.h>
13-
//#import <AVFoundation/AVAudioSession.h>
13+
#import <PushKit/PushKit.h>
1414

15-
@interface CallKeep: NSObject<FlutterStreamHandler, CXProviderDelegate>
15+
@interface CallKeep: NSObject<CXProviderDelegate, PKPushRegistryDelegate>
16+
@property (nonatomic, strong, nullable) CXCallController *callKeepCallController;
17+
@property (nonatomic, strong, nullable) CXProvider *callKeepProvider;
18+
@property (nonatomic, strong, nullable) FlutterMethodChannel* eventChannel;
1619

17-
@property (nonatomic, strong) FlutterMethodChannel* eventChannel;
18-
@property (nonatomic, strong) CXCallController *callKeepCallController;
19-
@property (nonatomic, strong) CXProvider *callKeepProvider;
20+
- (BOOL)handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result;
2021

22+
+ (BOOL)application:(UIApplication * _Nonnull)application
23+
openURL:(NSURL * _Nonnull)url
24+
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> * _Nonnull)options NS_AVAILABLE_IOS(9_0);
2125

22-
- (BOOL)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
26+
+ (BOOL)application:(UIApplication * _Nonnull)application
27+
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
28+
restorationHandler:(void(^ _Nonnull)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler;
2329

24-
+ (BOOL)application:(UIApplication *)application
25-
openURL:(NSURL *)url
26-
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options NS_AVAILABLE_IOS(9_0);
27-
28-
+ (BOOL)application:(UIApplication *)application
29-
continueUserActivity:(NSUserActivity *)userActivity
30-
restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler;
31-
32-
+ (void)reportNewIncomingCall:(NSString *)uuidString
33-
handle:(NSString *)handle
34-
handleType:(NSString *)handleType
30+
+ (void)reportNewIncomingCall:(NSString * _Nonnull)uuidString
31+
handle:(NSString * _Nonnull)handle
32+
handleType:(NSString * _Nonnull)handleType
3533
hasVideo:(BOOL)hasVideo
3634
localizedCallerName:(NSString * _Nullable)localizedCallerName
3735
fromPushKit:(BOOL)fromPushKit
3836
payload:(NSDictionary * _Nullable)payload;
3937

40-
+ (void)reportNewIncomingCall:(NSString *)uuidString
41-
handle:(NSString *)handle
42-
handleType:(NSString *)handleType
38+
+ (void)reportNewIncomingCall:(NSString * _Nonnull)uuidString
39+
handle:(NSString * _Nonnull)handle
40+
handleType:(NSString * _Nonnull)handleType
4341
hasVideo:(BOOL)hasVideo
4442
localizedCallerName:(NSString * _Nullable)localizedCallerName
4543
fromPushKit:(BOOL)fromPushKit
4644
payload:(NSDictionary * _Nullable)payload
4745
withCompletionHandler:(void (^_Nullable)(void))completion;
4846

49-
+ (void)endCallWithUUID:(NSString *)uuidString
47+
+ (void)endCallWithUUID:(NSString * _Nonnull)uuidString
5048
reason:(int)reason;
5149

52-
+ (BOOL)isCallActive:(NSString *)uuidString;
50+
+ (BOOL)isCallActive:(NSString * _Nonnull)uuidString;
5351

5452
@end

0 commit comments

Comments
 (0)