From 740023e1d5fbf0a5f12c6e30775402c2b965ed7b Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Wed, 11 Nov 2020 12:57:55 +0800
Subject: [PATCH 01/60] 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.
---
README.md | 3 +-
.../java/io/wazo/callkeep/CallKeepModule.java | 7 +-
.../wazo/callkeep/VoiceConnectionService.java | 2 +-
example/.metadata | 10 --
example/android/app/build.gradle | 7 ++
.../android/app/src/main/AndroidManifest.xml | 2 +-
.../flutter_callkeep_example/Application.java | 28 +++++
example/android/build.gradle | 1 +
example/ios/Runner/AppDelegate.m | 12 +-
example/ios/Runner/Info.plist | 1 -
example/lib/main.dart | 119 +++++++++++++++++-
example/pubspec.yaml | 3 +
ios/Classes/CallKeep.h | 42 +++----
ios/Classes/CallKeep.m | 84 ++++++++++---
ios/Classes/FlutterCallkeepPlugin.h | 14 +--
lib/src/actions.dart | 7 ++
lib/src/api.dart | 37 +++---
17 files changed, 287 insertions(+), 92 deletions(-)
delete mode 100644 example/.metadata
create mode 100644 example/android/app/src/main/java/com/github/cloudwebrtc/flutter_callkeep_example/Application.java
diff --git a/README.md b/README.md
index 7daafac2..923a1570 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
# callkeep
-iOS CallKit and Android ConnectionService for Flutter
+* iOS CallKit and Android ConnectionService for Flutter
+* Support FCM and PushKit
diff --git a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
index 6dc2ed92..31fe6ef8 100644
--- a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
+++ b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
@@ -217,7 +217,6 @@ public boolean HandleMethodCall(@NonNull MethodCall call, @NonNull Result result
public void setup(ConstraintsMap options) {
VoiceConnectionService.setAvailable(false);
this._settings = options;
-
if (isConnectionServiceAvailable()) {
this.registerPhoneAccount();
this.registerEvents();
@@ -598,7 +597,7 @@ private void registerPhoneAccount(Context appContext) {
}
private void sendEventToFlutter(String eventName, @Nullable ConstraintsMap params) {
- _eventChannel.invokeMethod(eventName, params != null? params.toMap() : null);
+ _eventChannel.invokeMethod(eventName, params.toMap());
}
private String getApplicationName(Context appContext) {
@@ -735,10 +734,10 @@ public void onReceive(Context context, Intent intent) {
sendEventToFlutter("CallKeepDidReceiveStartCallAction", args);
break;
case ACTION_AUDIO_SESSION:
- sendEventToFlutter("CallKeepDidActivateAudioSession", null);
+ sendEventToFlutter("CallKeepDidActivateAudioSession", args);
break;
case ACTION_CHECK_REACHABILITY:
- sendEventToFlutter("CallKeepCheckReachability", null);
+ sendEventToFlutter("CallKeepCheckReachability", args);
break;
case ACTION_WAKE_APP:
Intent headlessIntent = new Intent(_context, CallKeepBackgroundMessagingService.class);
diff --git a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
index c1dc7078..ad5f870b 100644
--- a/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
+++ b/android/src/main/java/io/wazo/callkeep/VoiceConnectionService.java
@@ -56,7 +56,7 @@ public class VoiceConnectionService extends ConnectionService {
private static Boolean isReachable;
private static String notReachableCallUuid;
private static ConnectionRequest currentConnectionRequest;
- private static PhoneAccountHandle phoneAccountHandle;
+ private static PhoneAccountHandle phoneAccountHandle = null;
private static String TAG = "RNCK:VoiceConnectionService";
public static Map currentConnections = new HashMap<>();
public static Boolean hasOutgoingCall = false;
diff --git a/example/.metadata b/example/.metadata
deleted file mode 100644
index e7f82014..00000000
--- a/example/.metadata
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file tracks properties of this Flutter project.
-# Used by Flutter tool to assess capabilities and perform upgrades etc.
-#
-# This file should be version controlled and should not be manually edited.
-
-version:
- revision: 49fac9a885df6f1029799fd208fdd79df019c387
- channel: master
-
-project_type: app
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 1664094e..3380907e 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -22,8 +22,15 @@ if (flutterVersionName == null) {
}
apply plugin: 'com.android.application'
+apply plugin: 'com.google.gms.google-services'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+dependencies {
+ // Add the dependencies for any other desired Firebase products
+ // https://firebase.google.com/docs/android/setup#available-libraries
+ implementation 'com.google.firebase:firebase-messaging:20.3.0'
+}
+
android {
compileSdkVersion 28
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index ab16d963..497f0156 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -27,7 +27,7 @@
> * __nullable restorableObjects))restorationHandler {
return [CallKeep application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
-
}
+
@end
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 99ce784c..0d8a38cc 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -24,7 +24,6 @@
UIBackgroundModes
- fetch
remote-notification
voip
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 41532905..ec3d9514 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,10 +1,75 @@
import 'dart:async';
+import 'dart:io';
+import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:callkeep/callkeep.dart';
import 'package:uuid/uuid.dart';
+/// For fcm background message handler.
+final FlutterCallkeep _callKeep = FlutterCallkeep();
+bool _callKeepInited = false;
+
+Future myBackgroundMessageHandler(Map message) {
+ print('backgroundMessage: message => ${message.toString()}');
+
+ var number = message['data']['body'] as String;
+ final callUUID = Uuid().v4();
+ _callKeep.on(CallKeepPerformAnswerCallAction(),
+ (CallKeepPerformAnswerCallAction event) {
+ print(
+ 'backgroundMessage: CallKeepPerformAnswerCallAction ${event.callUUID}');
+ _callKeep.startCall(event.callUUID, number, number);
+
+ Timer(const Duration(seconds: 1), () {
+ print('[setCurrentCallActive] $callUUID, number: $number');
+ _callKeep.setCurrentCallActive(callUUID);
+ });
+ //_callKeep.endCall(event.callUUID);
+ });
+
+ _callKeep.on(CallKeepPerformEndCallAction(),
+ (CallKeepPerformEndCallAction event) {
+ print('backgroundMessage: CallKeepPerformEndCallAction ${event.callUUID}');
+ });
+ if (!_callKeepInited) {
+ _callKeep.setup({
+ 'ios': {
+ 'appName': 'CallKeepDemo',
+ },
+ 'android': {
+ 'alertTitle': 'Permissions required',
+ 'alertDescription':
+ 'This application needs to access your phone accounts',
+ 'cancelButton': 'Cancel',
+ 'okButton': 'ok',
+ },
+ });
+ _callKeepInited = true;
+ }
+
+ print('backgroundMessage: displayIncomingCall ($number)');
+ _callKeep.displayIncomingCall(callUUID, number);
+ _callKeep.backToForeground();
+ /*
+
+ if (message.containsKey('data')) {
+ // Handle data message
+ final dynamic data = message['data'];
+ }
+
+ if (message.containsKey('notification')) {
+ // Handle notification message
+ final dynamic notification = message['notification'];
+ print('notification => ${notification.toString()}');
+ }
+
+ // Or do other work.
+ */
+ return null;
+}
+
void main() {
runApp(MyApp());
}
@@ -35,8 +100,17 @@ class Call {
class _MyAppState extends State {
final FlutterCallkeep _callKeep = FlutterCallkeep();
Map calls = {};
-
String newUUID() => Uuid().v4();
+ final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
+
+ void iOS_Permission() {
+ _firebaseMessaging.requestNotificationPermissions(
+ IosNotificationSettings(sound: true, badge: true, alert: true));
+ _firebaseMessaging.onIosSettingsRegistered
+ .listen((IosNotificationSettings settings) {
+ print('Settings registered: $settings');
+ });
+ }
void removeCall(String callUUID) {
setState(() {
@@ -176,9 +250,23 @@ class _MyAppState extends State {
handleType: 'number', hasVideo: false);
}
+ void didDisplayIncomingCall(CallKeepDidDisplayIncomingCall event) {
+ var callUUID = event.callUUID;
+ var number = event.handle;
+ print('[displayIncomingCall] $callUUID number: $number');
+ setState(() {
+ calls[callUUID] = Call(number);
+ });
+ }
+
+ void onPushKitToken(CallKeepPushKitToken event) {
+ print('[onPushKitToken] token => ${event.token}');
+ }
+
@override
void initState() {
super.initState();
+ _callKeep.on(CallKeepDidDisplayIncomingCall(), didDisplayIncomingCall);
_callKeep.on(CallKeepPerformAnswerCallAction(), answerCall);
_callKeep.on(CallKeepDidPerformDTMFAction(), didPerformDTMFAction);
_callKeep.on(
@@ -187,6 +275,7 @@ class _MyAppState extends State {
_callKeep.on(
CallKeepDidPerformSetMutedCallAction(), didPerformSetMutedCallAction);
_callKeep.on(CallKeepPerformEndCallAction(), endCall);
+ _callKeep.on(CallKeepPushKitToken(), onPushKitToken);
_callKeep.setup({
'ios': {
@@ -200,6 +289,34 @@ class _MyAppState extends State {
'okButton': 'ok',
},
});
+
+ if (Platform.isAndroid) {
+ //if (isIOS) iOS_Permission();
+ // _firebaseMessaging.requestNotificationPermissions();
+
+ _firebaseMessaging.getToken().then((token) {
+ print('[FCM] token => ' + token);
+ });
+
+ _firebaseMessaging.configure(
+ onMessage: (Map message) async {
+ print('onMessage: $message');
+ if (message.containsKey('data')) {
+ // Handle data message
+ final dynamic data = message['data'];
+ var number = data['body'] as String;
+ await displayIncomingCall(number);
+ }
+ },
+ onBackgroundMessage: myBackgroundMessageHandler,
+ onLaunch: (Map message) async {
+ print('onLaunch: $message');
+ },
+ onResume: (Map message) async {
+ print('onResume: $message');
+ },
+ );
+ }
}
Widget buildCallingWidgets() {
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index de0cdafa..ab5a7ef7 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,5 +1,6 @@
name: flutter_callkeep_example
description: Demonstrates how to use the flutter_callkeep plugin.
+version: 0.1.1
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
@@ -24,6 +25,8 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.0
uuid: ^2.0.2
+ firebase_messaging: ^7.0.0
+
dev_dependencies:
flutter_test:
sdk: flutter
diff --git a/ios/Classes/CallKeep.h b/ios/Classes/CallKeep.h
index 3a8daa11..493ad183 100644
--- a/ios/Classes/CallKeep.h
+++ b/ios/Classes/CallKeep.h
@@ -10,45 +10,43 @@
#import
#import
#import
-//#import
+#import
-@interface CallKeep: NSObject
+@interface CallKeep: NSObject
+@property (nonatomic, strong, nullable) CXCallController *callKeepCallController;
+@property (nonatomic, strong, nullable) CXProvider *callKeepProvider;
+@property (nonatomic, strong, nullable) FlutterMethodChannel* eventChannel;
-@property (nonatomic, strong) FlutterMethodChannel* eventChannel;
-@property (nonatomic, strong) CXCallController *callKeepCallController;
-@property (nonatomic, strong) CXProvider *callKeepProvider;
+- (BOOL)handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result;
++ (BOOL)application:(UIApplication * _Nonnull)application
+ openURL:(NSURL * _Nonnull)url
+ options:(NSDictionary * _Nonnull)options NS_AVAILABLE_IOS(9_0);
-- (BOOL)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
++ (BOOL)application:(UIApplication * _Nonnull)application
+continueUserActivity:(NSUserActivity * _Nonnull)userActivity
+ restorationHandler:(void(^ _Nonnull)(NSArray> * __nullable restorableObjects))restorationHandler;
-+ (BOOL)application:(UIApplication *)application
- openURL:(NSURL *)url
- options:(NSDictionary *)options NS_AVAILABLE_IOS(9_0);
-
-+ (BOOL)application:(UIApplication *)application
-continueUserActivity:(NSUserActivity *)userActivity
- restorationHandler:(void(^)(NSArray> * __nullable restorableObjects))restorationHandler;
-
-+ (void)reportNewIncomingCall:(NSString *)uuidString
- handle:(NSString *)handle
- handleType:(NSString *)handleType
++ (void)reportNewIncomingCall:(NSString * _Nonnull)uuidString
+ handle:(NSString * _Nonnull)handle
+ handleType:(NSString * _Nonnull)handleType
hasVideo:(BOOL)hasVideo
localizedCallerName:(NSString * _Nullable)localizedCallerName
fromPushKit:(BOOL)fromPushKit
payload:(NSDictionary * _Nullable)payload;
-+ (void)reportNewIncomingCall:(NSString *)uuidString
- handle:(NSString *)handle
- handleType:(NSString *)handleType
++ (void)reportNewIncomingCall:(NSString * _Nonnull)uuidString
+ handle:(NSString * _Nonnull)handle
+ handleType:(NSString * _Nonnull)handleType
hasVideo:(BOOL)hasVideo
localizedCallerName:(NSString * _Nullable)localizedCallerName
fromPushKit:(BOOL)fromPushKit
payload:(NSDictionary * _Nullable)payload
withCompletionHandler:(void (^_Nullable)(void))completion;
-+ (void)endCallWithUUID:(NSString *)uuidString
++ (void)endCallWithUUID:(NSString * _Nonnull)uuidString
reason:(int)reason;
-+ (BOOL)isCallActive:(NSString *)uuidString;
++ (BOOL)isCallActive:(NSString * _Nonnull)uuidString;
@end
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index 74d478f6..b96d7b56 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -6,14 +6,9 @@
// SPDX-License-Identifier: ISC, MIT
//
#import
-#import "CallKeep.h"
-#import
+#import
-#ifdef DEBUG
-static int const OUTGOING_CALL_WAKEUP_DELAY = 10;
-#else
-static int const OUTGOING_CALL_WAKEUP_DELAY = 5;
-#endif
+#import "CallKeep.h"
static NSString *const CallKeepHandleStartCallNotification = @"CallKeepHandleStartCallNotification";
static NSString *const CallKeepDidReceiveStartCallAction = @"CallKeepDidReceiveStartCallAction";
@@ -28,6 +23,7 @@
static NSString *const CallKeepProviderReset = @"CallKeepProviderReset";
static NSString *const CallKeepCheckReachability = @"CallKeepCheckReachability";
static NSString *const CallKeepDidLoadWithEvents = @"CallKeepDidLoadWithEvents";
+static NSString *const CallKeepPushKitToken = @"CallKeepPushKitToken";
@implementation CallKeep
{
@@ -162,15 +158,11 @@ - (void)stopObserving
}
- (void)sendEventWithName:(NSString *)name body:(id)body {
- [self.eventChannel invokeMethod:name arguments:body];
+ [self.eventChannel invokeMethod:name arguments:body];
}
- (void)sendEventWithNameWrapper:(NSString *)name body:(id)body {
- if (_hasListeners) {
- [self sendEventWithName:name body:body];
- } else {
- [self.eventChannel invokeMethod:name arguments:body];
- }
+ [self sendEventWithName:name body:body];
}
+ (void)initCallKitProvider {
@@ -196,8 +188,62 @@ -(void)setup:(NSDictionary *)options
self.callKeepProvider = sharedProvider;
[self.callKeepProvider setDelegate:self queue:nil];
+ [self voipRegistration];
+}
+
+#pragma mark - PushKit
+
+-(void)voipRegistration
+{
+ PKPushRegistry* voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
+ voipRegistry.delegate = self;
+ voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
+}
+
+- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type {
+ const unsigned *tokenBytes = [pushCredentials.token bytes];
+ NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
+ ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
+ ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
+ ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
+
+ NSLog(@"\n[VoIP Token]: %@\n\n",hexToken);
+
+ [self sendEventWithNameWrapper:CallKeepPushKitToken body:@{ @"token": hexToken }];
+}
+
+- (NSString *)createUUID {
+ CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
+ NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject));
+ CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuidObject);
+ CFRelease(uuidObject);
+ return [uuidStr lowercaseString];
+}
+
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
+ // Process the received push
+ NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type);
+ /* payload example.
+ {
+ "callkeep": {
+ "title": "Incoming Call",
+ "number": "+86186123456789"
+ }
+ }
+ */
+ NSDictionary *dic = payload.dictionaryPayload[@"callkeep"];
+ NSString *number = dic[@"number"];
+ [CallKeep reportNewIncomingCall:[self createUUID]
+ handle:number
+ handleType:@"number"
+ hasVideo:NO
+ localizedCallerName:@"hello"
+ fromPushKit:YES
+ payload:payload.dictionaryPayload
+ withCompletionHandler:^(){}];
}
+
-(void) checkIfBusyWithResult:(FlutterResult)result
{
#ifdef DEBUG
@@ -463,8 +509,8 @@ + (void)reportNewIncomingCall:(NSString *)uuidString
@"callUUID": uuidString,
@"handle": handle,
@"localizedCallerName": localizedCallerName ? localizedCallerName : @"",
- @"hasVideo": hasVideo ? @"1" : @"0",
- @"fromPushKit": fromPushKit ? @"1" : @"0",
+ @"hasVideo": @(hasVideo),
+ @"fromPushKit": @(fromPushKit),
@"payload": payload ? payload : @"",
}];
if (error == nil) {
@@ -673,7 +719,7 @@ - (void)providerDidReset:(CXProvider *)provider{
#endif
//this means something big changed, so tell the JS. The JS should
//probably respond by hanging up all calls.
- [self sendEventWithNameWrapper:CallKeepProviderReset body:nil];
+ [self sendEventWithNameWrapper:CallKeepProviderReset body:@{}];
}
// Starting outgoing call
@@ -694,7 +740,7 @@ - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallActio
-(void) reportUpdatedCall:(NSString *)uuidString contactIdentifier:(NSString *)contactIdentifier
{
#ifdef DEBUG
- NSLog(@"[CallKeep][reportUpdatedCall] contactIdentifier = %i", contactIdentifier);
+ NSLog(@"[CallKeep][reportUpdatedCall] contactIdentifier = %@", contactIdentifier);
#endif
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
@@ -772,7 +818,7 @@ - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession
[[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionInterruptionNotification object:nil userInfo:userInfo];
[self configureAudioSession];
- [self sendEventWithNameWrapper:CallKeepDidActivateAudioSession body:nil];
+ [self sendEventWithNameWrapper:CallKeepDidActivateAudioSession body:@{}];
}
- (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession
@@ -780,7 +826,7 @@ - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSessio
#ifdef DEBUG
NSLog(@"[CallKeep][CXProviderDelegate][provider:didDeactivateAudioSession]");
#endif
- [self sendEventWithNameWrapper:CallKeepDidDeactivateAudioSession body:nil];
+ [self sendEventWithNameWrapper:CallKeepDidDeactivateAudioSession body:@{}];
}
@end
diff --git a/ios/Classes/FlutterCallkeepPlugin.h b/ios/Classes/FlutterCallkeepPlugin.h
index 097fb9ad..3fd1329b 100644
--- a/ios/Classes/FlutterCallkeepPlugin.h
+++ b/ios/Classes/FlutterCallkeepPlugin.h
@@ -1,11 +1,11 @@
#import
@interface FlutterCallkeepPlugin : NSObject
-+ (instancetype)sharedInstance;
-+ (BOOL)application:(UIApplication *)application
- openURL:(NSURL *)url
- options:(NSDictionary *)options NS_AVAILABLE_IOS(9_0);
-- (BOOL)application:(UIApplication *)application
- continueUserActivity:(NSUserActivity *)userActivity
- restorationHandler:(void (^)(NSArray *_Nullable))restorationHandler;
++ (instancetype _Nullable)sharedInstance;
++ (BOOL)application:(UIApplication * _Nullable)application
+ openURL:(NSURL * _Nullable)url
+ options:(NSDictionary * _Nullable)options NS_AVAILABLE_IOS(9_0);
+- (BOOL)application:(UIApplication * _Nullable)application
+ continueUserActivity:(NSUserActivity * _Nullable)userActivity
+ restorationHandler:(void (^ __nullable)(NSArray *_Nullable))restorationHandler;
@end
diff --git a/lib/src/actions.dart b/lib/src/actions.dart
index ea8386eb..fef42eb5 100644
--- a/lib/src/actions.dart
+++ b/lib/src/actions.dart
@@ -86,3 +86,10 @@ class CallKeepCheckReachability extends EventType {
class CallKeepDidLoadWithEvents extends EventType {
CallKeepDidLoadWithEvents();
}
+
+class CallKeepPushKitToken extends EventType {
+ CallKeepPushKitToken();
+ CallKeepPushKitToken.fromMap(Map arguments)
+ : token = arguments['token'] as String;
+ String token;
+}
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 5791462b..2fc29c2f 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -20,18 +20,22 @@ bool get supportConnectionService =>
!isIOS && int.parse(Platform.version) >= 23;
class FlutterCallkeep extends EventManager {
- FlutterCallkeep() {
+ factory FlutterCallkeep() {
+ return _instance;
+ }
+ FlutterCallkeep._internal() {
_event.setMethodCallHandler(eventListener);
}
- BuildContext _context;
+ static final FlutterCallkeep _instance = FlutterCallkeep._internal();
static const MethodChannel _channel = MethodChannel('FlutterCallKeep.Method');
static const MethodChannel _event = MethodChannel('FlutterCallKeep.Event');
+ BuildContext _context;
Future setup(Map options) async {
if (!isIOS) {
- return _setupAndroid(options['android'] as Map);
+ await _setupAndroid(options['android'] as Map);
}
- return _setupIOS(options['ios'] as Map);
+ await _setupIOS(options['ios'] as Map);
}
Future registerPhoneAccount() async {
@@ -336,18 +340,16 @@ class FlutterCallkeep extends EventManager {
Future eventListener(MethodCall call) async {
print('[CallKeep] INFO: received event "${call.method}" ${call.arguments}');
+ final data = call.arguments as Map;
switch (call.method) {
case 'CallKeepDidReceiveStartCallAction':
- emit(CallKeepDidReceiveStartCallAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepDidReceiveStartCallAction.fromMap(data));
break;
case 'CallKeepPerformAnswerCallAction':
- emit(CallKeepPerformAnswerCallAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepPerformAnswerCallAction.fromMap(data));
break;
case 'CallKeepPerformEndCallAction':
- emit(CallKeepPerformEndCallAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepPerformEndCallAction.fromMap(data));
break;
case 'CallKeepDidActivateAudioSession':
emit(CallKeepDidActivateAudioSession());
@@ -356,20 +358,16 @@ class FlutterCallkeep extends EventManager {
emit(CallKeepDidActivateAudioSession());
break;
case 'CallKeepDidDisplayIncomingCall':
- emit(CallKeepDidDisplayIncomingCall.fromMap(
- call.arguments as Map));
+ emit(CallKeepDidDisplayIncomingCall.fromMap(data));
break;
case 'CallKeepDidPerformSetMutedCallAction':
- emit(CallKeepDidPerformSetMutedCallAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepDidPerformSetMutedCallAction.fromMap(data));
break;
case 'CallKeepDidToggleHoldAction':
- emit(CallKeepDidToggleHoldAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepDidToggleHoldAction.fromMap(data));
break;
case 'CallKeepDidPerformDTMFAction':
- emit(CallKeepDidPerformDTMFAction.fromMap(
- call.arguments as Map));
+ emit(CallKeepDidPerformDTMFAction.fromMap(data));
break;
case 'CallKeepProviderReset':
emit(CallKeepProviderReset());
@@ -380,6 +378,9 @@ class FlutterCallkeep extends EventManager {
case 'CallKeepDidLoadWithEvents':
emit(CallKeepDidLoadWithEvents());
break;
+ case 'CallKeepPushKitToken':
+ emit(CallKeepPushKitToken.fromMap(data));
+ break;
}
}
}
From 03cb0cd3bc99c93bd5243949222adc7dc22d68dc Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Wed, 11 Nov 2020 13:00:49 +0800
Subject: [PATCH 02/60] Upgrade version to 0.2.0.
---
CHANGELOG.md | 7 +++++++
pubspec.yaml | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dfa8877..e34cdeb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
# Changelog
-----------------------------------------------
+[0.2.0] - 2020.11.11
+
+* Change FlutterCallKeep as a singleton.
+* Add CallKeepPushKitToken event for iOS.
+* Add firebase_messaging to example.
+* Support waking CallKeep from PushKit when the app is closed.
+
[0.1.1] - 2020.09.17
* Fix compile error for iOS.
diff --git a/pubspec.yaml b/pubspec.yaml
index 8db27edc..65a568b2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.1.1
+version: 0.2.0
author: duanweiwei1982@gmail.com
homepage: https://github.com/flutter-webrtc/callkeep
From b3084135092c270e8589a8187c3e32d59ef66d0a Mon Sep 17 00:00:00 2001
From: Christian
Date: Tue, 1 Dec 2020 12:28:58 +0100
Subject: [PATCH 03/60] fix: Missing null check (#30)
---
lib/src/api.dart | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 2fc29c2f..ade55d62 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -70,7 +70,7 @@ class FlutterCallkeep extends EventManager {
Future _hasDefaultPhoneAccount(Map options) async {
final hasDefault = await _checkDefaultPhoneAccount();
final shouldOpenAccounts = await _alert(options, hasDefault);
- if (shouldOpenAccounts) {
+ if (shouldOpenAccounts == true) {
await _openPhoneAccounts();
}
}
From 59306ca20482f2272682aa8331e237a8f7e34823 Mon Sep 17 00:00:00 2001
From: Pierre-Monier <65488471+Pierre-Monier@users.noreply.github.com>
Date: Wed, 23 Dec 2020 00:34:48 +0100
Subject: [PATCH 04/60] fix: change parameter handle to number (#39)
* fix: change parameter handle to number , call.argument("number") was undefined in HandleMethodCall for startCall case
* fix(ios): change handle to number
---
ios/Classes/CallKeep.m | 2 +-
lib/src/api.dart | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index b96d7b56..245665c1 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -88,7 +88,7 @@ - (BOOL)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
result(nil);
}
else if ([@ "startCall" isEqualToString:method]) {
- [self startCall:argsMap[@"uuid"] handle:argsMap[@"handle"] contactIdentifier:argsMap[@"callerName"] handleType:argsMap[@"handleType"] video:[argsMap[@"hasVideo"] boolValue]];
+ [self startCall:argsMap[@"uuid"] handle:argsMap[@"number"] contactIdentifier:argsMap[@"callerName"] handleType:argsMap[@"handleType"] video:[argsMap[@"hasVideo"] boolValue]];
result(nil);
}
else if ([@"isCallActive" isEqualToString:method]) {
diff --git a/lib/src/api.dart b/lib/src/api.dart
index ade55d62..8ddbe3df 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -104,19 +104,19 @@ class FlutterCallkeep extends EventManager {
}
}
- Future startCall(String uuid, String handle, String callerName,
+ Future startCall(String uuid, String number, String callerName,
{String handleType = 'number', bool hasVideo = false}) async {
if (!isIOS) {
await _channel.invokeMethod('startCall', {
'uuid': uuid,
- 'handle': handle,
+ 'number': number,
'callerName': callerName
});
return;
}
await _channel.invokeMethod('startCall', {
'uuid': uuid,
- 'handle': handle,
+ 'number': number,
'callerName': callerName,
'handleType': handleType,
'hasVideo': hasVideo
From ddef4ba9c6c4ded2c0c1ef4c53aabcc88f9fc56c Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Wed, 23 Dec 2020 16:11:11 +0800
Subject: [PATCH 05/60] Upgrade version to 0.2.1.
---
CHANGELOG.md | 5 +++++
pubspec.yaml | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e34cdeb2..5b5603f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
# Changelog
-----------------------------------------------
+[0.2.1] - 2020.12.23
+
+* Fix: Missing null check.
+* Fix: change parameter handle to number.
+
[0.2.0] - 2020.11.11
* Change FlutterCallKeep as a singleton.
diff --git a/pubspec.yaml b/pubspec.yaml
index 65a568b2..37d7c597 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.0
+version: 0.2.1
author: duanweiwei1982@gmail.com
homepage: https://github.com/flutter-webrtc/callkeep
From b3dd0b25326927854cb9b701edfaa258f00d017c Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Thu, 24 Dec 2020 08:24:36 +0800
Subject: [PATCH 06/60] Add more field for pushkit playload.
---
ios/Classes/CallKeep.m | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index 245665c1..eb1dca4c 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -226,18 +226,30 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
/* payload example.
{
"callkeep": {
- "title": "Incoming Call",
- "number": "+86186123456789"
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
+ },
+ "extra": {
+ "foo": "bar",
+ "key": "value",
}
}
*/
NSDictionary *dic = payload.dictionaryPayload[@"callkeep"];
- NSString *number = dic[@"number"];
+ NSString *number = dic[@"caller_id"];
+ NSString *localizedCallerName = dic[@"caller_name"];
+ BOOL hasVideo = [dic[@"has_video"] boolValue];
+ NSString *handleType = dic[@"caller_id_type"];
+
+ //NSDictionary *extra = payload.dictionaryPayload[@"extra"];
+
[CallKeep reportNewIncomingCall:[self createUUID]
handle:number
- handleType:@"number"
- hasVideo:NO
- localizedCallerName:@"hello"
+ handleType:handleType
+ hasVideo:hasVideo
+ localizedCallerName:localizedCallerName
fromPushKit:YES
payload:payload.dictionaryPayload
withCompletionHandler:^(){}];
From cfb8f3fe411693ea694c315442862eb2fb11437b Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 26 Dec 2020 23:22:27 +0800
Subject: [PATCH 07/60] Add push payload format to README.md.
---
README.md | 19 +++++++++++++++++
ios/Classes/CallKeep.m | 46 ++++++++++++++++++++++++------------------
2 files changed, 45 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index 923a1570..6614c5cc 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,22 @@
# callkeep
+
* iOS CallKit and Android ConnectionService for Flutter
* Support FCM and PushKit
+
+## push payload
+
+```json
+{
+ "callkeep": {
+ "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
+ },
+ "extra": {
+ "foo": "bar",
+ "key": "value",
+ }
+}
+```
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index eb1dca4c..cd590d67 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -224,32 +224,38 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
// Process the received push
NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type);
/* payload example.
- {
- "callkeep": {
- "caller_id": "+8618612345678",
- "caller_name": "hello",
- "caller_id_type": "number",
- "has_video": false,
- },
- "extra": {
- "foo": "bar",
- "key": "value",
- }
- }
+ {
+ "callkeep": {
+ "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
+ },
+ "extra": {
+ "foo": "bar",
+ "key": "value",
+ }
+ }
*/
NSDictionary *dic = payload.dictionaryPayload[@"callkeep"];
- NSString *number = dic[@"caller_id"];
- NSString *localizedCallerName = dic[@"caller_name"];
+ NSString *callerId = dic[@"caller_id"];
+ NSString *callerName = dic[@"caller_name"];
BOOL hasVideo = [dic[@"has_video"] boolValue];
- NSString *handleType = dic[@"caller_id_type"];
-
+ NSString *callerIdType = dic[@"caller_id_type"];
+ NSString *uuid = dic[@"uuid"];
+
+ if( uuid == nil) {
+ uuid = [self createUUID];
+ }
+
//NSDictionary *extra = payload.dictionaryPayload[@"extra"];
- [CallKeep reportNewIncomingCall:[self createUUID]
- handle:number
- handleType:handleType
+ [CallKeep reportNewIncomingCall:uuid
+ handle:callerId
+ handleType:callerIdType
hasVideo:hasVideo
- localizedCallerName:localizedCallerName
+ localizedCallerName:callerName
fromPushKit:YES
payload:payload.dictionaryPayload
withCompletionHandler:^(){}];
From 0933f2b626c3fcccc0306b3ac0d479071fb3d5a1 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sun, 27 Dec 2020 00:23:18 +0800
Subject: [PATCH 08/60] update.
---
CHANGELOG.md | 4 ++++
README.md | 16 +++++-----------
example/lib/main.dart | 33 +++++++++++++++++++++++++++------
ios/Classes/CallKeep.m | 24 ++++++++++--------------
pubspec.yaml | 2 +-
5 files changed, 47 insertions(+), 32 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b5603f9..c92fb115 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# Changelog
-----------------------------------------------
+[0.2.2] - 2020.12.27
+
+* Update json format for push payload.
+
[0.2.1] - 2020.12.23
* Fix: Missing null check.
diff --git a/README.md b/README.md
index 6614c5cc..975f9772 100644
--- a/README.md
+++ b/README.md
@@ -7,16 +7,10 @@
```json
{
- "callkeep": {
- "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
- "caller_id": "+8618612345678",
- "caller_name": "hello",
- "caller_id_type": "number",
- "has_video": false,
- },
- "extra": {
- "foo": "bar",
- "key": "value",
- }
+ "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
}
```
diff --git a/example/lib/main.dart b/example/lib/main.dart
index ec3d9514..60f65981 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -11,19 +11,39 @@ import 'package:uuid/uuid.dart';
final FlutterCallkeep _callKeep = FlutterCallkeep();
bool _callKeepInited = false;
+/*
+{
+ "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
+
+ "extra": {
+ "foo": "bar",
+ "key": "value",
+ }
+}
+*/
+
Future myBackgroundMessageHandler(Map message) {
print('backgroundMessage: message => ${message.toString()}');
+ var payload = message['data'];
+ var callerId = payload['caller_id'] as String;
+ var callerNmae = payload['caller_name'] as String;
+ var uuid = payload['uuid'] as String;
+ var hasVideo = payload['has_video'] == "true";
- var number = message['data']['body'] as String;
- final callUUID = Uuid().v4();
+ final callUUID = uuid ?? Uuid().v4();
_callKeep.on(CallKeepPerformAnswerCallAction(),
(CallKeepPerformAnswerCallAction event) {
print(
'backgroundMessage: CallKeepPerformAnswerCallAction ${event.callUUID}');
- _callKeep.startCall(event.callUUID, number, number);
+ _callKeep.startCall(event.callUUID, callerId, callerNmae);
Timer(const Duration(seconds: 1), () {
- print('[setCurrentCallActive] $callUUID, number: $number');
+ print(
+ '[setCurrentCallActive] $callUUID, callerId: $callerId, callerName: $callerNmae');
_callKeep.setCurrentCallActive(callUUID);
});
//_callKeep.endCall(event.callUUID);
@@ -49,8 +69,9 @@ Future myBackgroundMessageHandler(Map message) {
_callKeepInited = true;
}
- print('backgroundMessage: displayIncomingCall ($number)');
- _callKeep.displayIncomingCall(callUUID, number);
+ print('backgroundMessage: displayIncomingCall ($callerId)');
+ _callKeep.displayIncomingCall(callUUID, callerId,
+ localizedCallerName: callerNmae, hasVideo: hasVideo);
_callKeep.backToForeground();
/*
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index cd590d67..681a52c1 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -225,25 +225,21 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type);
/* payload example.
{
- "callkeep": {
- "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
- "caller_id": "+8618612345678",
- "caller_name": "hello",
- "caller_id_type": "number",
- "has_video": false,
- },
- "extra": {
- "foo": "bar",
- "key": "value",
- }
+ "uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
+ "caller_id": "+8618612345678",
+ "caller_name": "hello",
+ "caller_id_type": "number",
+ "has_video": false,
}
*/
- NSDictionary *dic = payload.dictionaryPayload[@"callkeep"];
+ NSDictionary *dic = payload.dictionaryPayload;
+
+ NSString *uuid = dic[@"uuid"];
NSString *callerId = dic[@"caller_id"];
NSString *callerName = dic[@"caller_name"];
BOOL hasVideo = [dic[@"has_video"] boolValue];
NSString *callerIdType = dic[@"caller_id_type"];
- NSString *uuid = dic[@"uuid"];
+
if( uuid == nil) {
uuid = [self createUUID];
@@ -257,7 +253,7 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
hasVideo:hasVideo
localizedCallerName:callerName
fromPushKit:YES
- payload:payload.dictionaryPayload
+ payload:dic
withCompletionHandler:^(){}];
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 37d7c597..0288f019 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.1
+version: 0.2.2
author: duanweiwei1982@gmail.com
homepage: https://github.com/flutter-webrtc/callkeep
From b09080e5ed1e0fde09b64c006a3648f818240986 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Thu, 7 Jan 2021 23:18:18 +0800
Subject: [PATCH 09/60] Fix backToForeground method.
---
.../main/java/io/wazo/callkeep/CallKeepModule.java | 11 +++++++----
lib/src/api.dart | 5 +++--
2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
index 31fe6ef8..c4773adb 100644
--- a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
+++ b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
@@ -544,7 +544,7 @@ public static Boolean isConnectionServiceAvailable() {
@SuppressLint("WrongConstant")
public void backToForeground(@NonNull MethodChannel.Result result) {
Context context = getAppContext();
- String packageName = context.getApplicationContext().getPackageName();
+ String packageName = context.getPackageName();
Intent focusIntent = context.getPackageManager().getLaunchIntentForPackage(packageName).cloneFilter();
Activity activity = this._currentActivity;
boolean isOpened = activity != null;
@@ -557,10 +557,13 @@ public void backToForeground(@NonNull MethodChannel.Result result) {
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED +
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD +
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-
- this._currentActivity.startActivity(focusIntent);
+ if (activity != null) {
+ activity.startActivity(focusIntent);
+ } else {
+ context.startActivity(focusIntent);
+ }
}
- result.success(null);
+ result.success(isOpened);
}
private void initializeTelecomManager() {
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 8ddbe3df..209c3f2c 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -253,10 +253,11 @@ class FlutterCallkeep extends EventManager {
Future backToForeground() async {
if (isIOS) {
- return;
+ return false;
}
- await _channel.invokeMethod('backToForeground', {});
+ return await _channel
+ .invokeMethod('backToForeground', {});
}
Future _setupIOS(Map options) async {
From 0046d4e0e12dfe5499170eeead833a0959bb437f Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Fri, 8 Jan 2021 00:15:29 +0800
Subject: [PATCH 10/60] upgrade version to 0.2.3.
---
CHANGELOG.md | 4 ++++
pubspec.yaml | 3 +--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c92fb115..cbfe6a40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
# Changelog
-----------------------------------------------
+[0.2.3] - 2021.01.08
+
+* Fix backToForeground method.
+
[0.2.2] - 2020.12.27
* Update json format for push payload.
diff --git a/pubspec.yaml b/pubspec.yaml
index 0288f019..a9537466 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.2
-author: duanweiwei1982@gmail.com
+version: 0.2.3
homepage: https://github.com/flutter-webrtc/callkeep
environment:
From 0090d26916b3ab4ffa02f0e7f27b7d1552f9b5c0 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Fri, 8 Jan 2021 00:26:56 +0800
Subject: [PATCH 11/60] hotfix.
---
lib/src/api.dart | 2 +-
pubspec.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 209c3f2c..8bdd47fb 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -251,7 +251,7 @@ class FlutterCallkeep extends EventManager {
'CallKeep.reportUpdatedCall was called from unsupported OS');
}
- Future backToForeground() async {
+ Future backToForeground() async {
if (isIOS) {
return false;
}
diff --git a/pubspec.yaml b/pubspec.yaml
index a9537466..89db4b76 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.3
+version: 0.2.3+1
homepage: https://github.com/flutter-webrtc/callkeep
environment:
From 27690ce90cc48e3d1c75a2fb22feea8c8f189c76 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 10 Jan 2021 09:48:10 +0800
Subject: [PATCH 12/60] chore(deps): update dependency gradle to v6 (#32)
Co-authored-by: Renovate Bot
---
android/gradle/wrapper/gradle-wrapper.properties | 2 +-
example/android/gradle/wrapper/gradle-wrapper.properties | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 01a286e9..5359c772 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index 296b146b..601e2a7f 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
From 6de659d918a3a3e552f688fce0ac09156b1082bd Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Sun, 10 Jan 2021 09:49:22 +0800
Subject: [PATCH 13/60] Delete gradle-wrapper.properties
---
android/gradle/wrapper/gradle-wrapper.properties | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 android/gradle/wrapper/gradle-wrapper.properties
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 5359c772..00000000
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
From 4aede74a8b3b1fd33638313a3ccf992f12ed14df Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 25 Jan 2021 09:13:40 +0800
Subject: [PATCH 14/60] chore(deps): update dependency gradle to v6.8.1 (#48)
Co-authored-by: Renovate Bot
---
example/android/gradle/wrapper/gradle-wrapper.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index 601e2a7f..dfbc8f4f 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip
From 7f23ec40b7dfb904c7f5597a3ed15c611eddf009 Mon Sep 17 00:00:00 2001
From: Pierre-Monier <65488471+Pierre-Monier@users.noreply.github.com>
Date: Fri, 29 Jan 2021 12:44:17 +0100
Subject: [PATCH 15/60] hasDefaultPhoneAccount give feedback about the user
choice (#49)
* fix: change parameter handle to number , call.argument("number") was undefined in HandleMethodCall for startCall case
* fix(ios): change handle to number
* feat: make hasDefaultPhoneAccount return a bool to have feedback on the telecom manager request
---
lib/src/api.dart | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 8bdd47fb..51b22225 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -53,13 +53,15 @@ class FlutterCallkeep extends EventManager {
return _channel.invokeMethod('registerEvents', {});
}
- Future hasDefaultPhoneAccount(
+ Future hasDefaultPhoneAccount(
BuildContext context, Map options) async {
_context = context;
if (!isIOS) {
- return _hasDefaultPhoneAccount(options);
+ return await _hasDefaultPhoneAccount(options);
}
- return;
+
+ // return true on iOS because we don't want to block the endUser
+ return true;
}
Future _checkDefaultPhoneAccount() async {
@@ -67,12 +69,14 @@ class FlutterCallkeep extends EventManager {
.invokeMethod('checkDefaultPhoneAccount', {});
}
- Future _hasDefaultPhoneAccount(Map options) async {
+ Future _hasDefaultPhoneAccount(Map options) async {
final hasDefault = await _checkDefaultPhoneAccount();
final shouldOpenAccounts = await _alert(options, hasDefault);
- if (shouldOpenAccounts == true) {
+ if (shouldOpenAccounts) {
await _openPhoneAccounts();
+ return true;
}
+ return false;
}
Future displayIncomingCall(String uuid, String handle,
From bf318ed6b8f5bba7b2660cce765943ff72a98458 Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Wed, 5 May 2021 19:25:39 +0800
Subject: [PATCH 16/60] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 975f9772..6f57dc57 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
# callkeep
+ [](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ)
* iOS CallKit and Android ConnectionService for Flutter
* Support FCM and PushKit
From a542fa17736694e8971f30b9610fa2f10b5f13ed Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Wed, 5 May 2021 19:28:33 +0800
Subject: [PATCH 17/60] Update README.md
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6f57dc57..72c8a4d3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# callkeep
- [](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ)
+
+[](https://opencollective.com/flutter-webrtc) [](https://pub.dartlang.org/packages/callkeep) [](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ)
* iOS CallKit and Android ConnectionService for Flutter
* Support FCM and PushKit
From cc57761da91c6e8814156c2e1f3110b2bd8b0910 Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Fri, 21 May 2021 20:37:06 +0800
Subject: [PATCH 18/60] Fix crash when appName is not set.
---
ios/Classes/CallKeep.m | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index 681a52c1..498e3d7b 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -580,7 +580,11 @@ + (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary*)settings
#ifdef DEBUG
NSLog(@"[CallKeep][getProviderConfiguration]");
#endif
- CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:settings[@"appName"]];
+ NSString *appName = @"Unknown App";
+ if (settings != nil) {
+ appName = settings[@"appName"];
+ }
+ CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:appName];
providerConfiguration.supportsVideo = YES;
providerConfiguration.maximumCallGroups = 3;
providerConfiguration.maximumCallsPerCallGroup = 1;
From f3e799af2b8c2cfc07fa6e78d5a33196a822ae40 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 22 May 2021 20:29:10 +0800
Subject: [PATCH 19/60] Upgrade version to 0.2.4.
---
CHANGELOG.md | 5 +++++
pubspec.yaml | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbfe6a40..8677e1b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
# Changelog
-----------------------------------------------
+[0.2.4] - 2021.01.08
+
+* Fix crash when appName is not set.
+* hasDefaultPhoneAccount give feedback about the user choice.
+
[0.2.3] - 2021.01.08
* Fix backToForeground method.
diff --git a/pubspec.yaml b/pubspec.yaml
index 89db4b76..4de89af2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.3+1
+version: 0.2.4
homepage: https://github.com/flutter-webrtc/callkeep
environment:
From 1052eda4858c3d45c9c4630ab58d27bc4fc3c393 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Fri, 11 Jun 2021 22:36:12 +0800
Subject: [PATCH 20/60] null safety.
---
lib/src/actions.dart | 34 ++++++++++----------
lib/src/api.dart | 74 ++++++++++++++++++++++++++++++--------------
pubspec.yaml | 4 +--
3 files changed, 69 insertions(+), 43 deletions(-)
diff --git a/lib/src/actions.dart b/lib/src/actions.dart
index fef42eb5..f87669ea 100644
--- a/lib/src/actions.dart
+++ b/lib/src/actions.dart
@@ -6,23 +6,23 @@ class CallKeepDidReceiveStartCallAction extends EventType {
: callUUID = arguments['callUUID'] as String,
handle = arguments['handle'] as String,
name = arguments['name'] as String;
- String callUUID;
- String handle;
- String name;
+ String? callUUID;
+ String? handle;
+ String? name;
}
class CallKeepPerformAnswerCallAction extends EventType {
CallKeepPerformAnswerCallAction();
CallKeepPerformAnswerCallAction.fromMap(Map arguments)
: callUUID = arguments['callUUID'] as String;
- String callUUID;
+ String? callUUID;
}
class CallKeepPerformEndCallAction extends EventType {
CallKeepPerformEndCallAction();
CallKeepPerformEndCallAction.fromMap(Map arguments)
: callUUID = arguments['callUUID'] as String;
- String callUUID;
+ String? callUUID;
}
class CallKeepDidActivateAudioSession extends EventType {
@@ -41,11 +41,11 @@ class CallKeepDidDisplayIncomingCall extends EventType {
localizedCallerName = arguments['localizedCallerName'] as String,
hasVideo = arguments['hasVideo'] as bool,
fromPushKit = arguments['fromPushKit'] as bool;
- String callUUID;
- String handle;
- String localizedCallerName;
- bool hasVideo;
- bool fromPushKit;
+ String? callUUID;
+ String? handle;
+ String? localizedCallerName;
+ bool? hasVideo;
+ bool? fromPushKit;
}
class CallKeepDidPerformSetMutedCallAction extends EventType {
@@ -53,8 +53,8 @@ class CallKeepDidPerformSetMutedCallAction extends EventType {
CallKeepDidPerformSetMutedCallAction.fromMap(Map arguments)
: callUUID = arguments['callUUID'] as String,
muted = arguments['muted'] as bool;
- String callUUID;
- bool muted;
+ String? callUUID;
+ bool? muted;
}
class CallKeepDidToggleHoldAction extends EventType {
@@ -62,8 +62,8 @@ class CallKeepDidToggleHoldAction extends EventType {
CallKeepDidToggleHoldAction.fromMap(Map arguments)
: callUUID = arguments['callUUID'] as String,
hold = arguments['hold'] as bool;
- String callUUID;
- bool hold;
+ String? callUUID;
+ bool? hold;
}
class CallKeepDidPerformDTMFAction extends EventType {
@@ -71,8 +71,8 @@ class CallKeepDidPerformDTMFAction extends EventType {
CallKeepDidPerformDTMFAction.fromMap(Map arguments)
: callUUID = arguments['callUUID'] as String,
digits = arguments['digits'] as String;
- String callUUID;
- String digits;
+ String? callUUID;
+ String? digits;
}
class CallKeepProviderReset extends EventType {
@@ -91,5 +91,5 @@ class CallKeepPushKitToken extends EventType {
CallKeepPushKitToken();
CallKeepPushKitToken.fromMap(Map arguments)
: token = arguments['token'] as String;
- String token;
+ String? token;
}
diff --git a/lib/src/api.dart b/lib/src/api.dart
index 51b22225..6d435b30 100644
--- a/lib/src/api.dart
+++ b/lib/src/api.dart
@@ -3,13 +3,14 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart'
show
- showDialog,
AlertDialog,
BuildContext,
FlatButton,
Navigator,
Text,
- Widget;
+ TextButton,
+ Widget,
+ showDialog;
import 'package:flutter/services.dart' show MethodChannel;
import 'actions.dart';
@@ -29,7 +30,7 @@ class FlutterCallkeep extends EventManager {
static final FlutterCallkeep _instance = FlutterCallkeep._internal();
static const MethodChannel _channel = MethodChannel('FlutterCallKeep.Method');
static const MethodChannel _event = MethodChannel('FlutterCallKeep.Event');
- BuildContext _context;
+ BuildContext? _context;
Future setup(Map options) async {
if (!isIOS) {
@@ -64,7 +65,7 @@ class FlutterCallkeep extends EventManager {
return true;
}
- Future _checkDefaultPhoneAccount() async {
+ Future _checkDefaultPhoneAccount() async {
return await _channel
.invokeMethod('checkDefaultPhoneAccount', {});
}
@@ -161,8 +162,14 @@ class FlutterCallkeep extends EventManager {
}
}
- Future isCallActive(String uuid) async => await _channel
- .invokeMethod('isCallActive', {'uuid': uuid});
+ Future isCallActive(String uuid) async {
+ var resp = await _channel
+ .invokeMethod('isCallActive', {'uuid': uuid});
+ if (resp != null) {
+ return resp;
+ }
+ return false;
+ }
Future endCall(String uuid) async => await _channel
.invokeMethod('endCall', {'uuid': uuid});
@@ -174,16 +181,24 @@ class FlutterCallkeep extends EventManager {
if (isIOS) {
return true;
}
- return await _channel
+ var resp = await _channel
.invokeMethod('hasPhoneAccount', {});
+ if (resp != null) {
+ return resp;
+ }
+ return false;
}
Future hasOutgoingCall() async {
if (isIOS) {
return true;
}
- return await _channel
+ var resp = await _channel
.invokeMethod('hasOutgoingCall', {});
+ if (resp != null) {
+ return resp;
+ }
+ return false;
}
Future setMutedCall(String uuid, bool shouldMute) async =>
@@ -221,7 +236,7 @@ class FlutterCallkeep extends EventManager {
}
Future updateDisplay(String uuid,
- {String displayName, String handle}) async =>
+ {required String displayName, required String handle}) async =>
await _channel.invokeMethod('updateDisplay', {
'uuid': uuid,
'displayName': displayName,
@@ -259,9 +274,12 @@ class FlutterCallkeep extends EventManager {
if (isIOS) {
return false;
}
-
- return await _channel
+ var resp = await _channel
.invokeMethod('backToForeground', {});
+ if (resp != null) {
+ return resp;
+ }
+ return false;
}
Future _setupIOS(Map options) async {
@@ -279,7 +297,7 @@ class FlutterCallkeep extends EventManager {
Future _setupAndroid(Map options) async {
await _channel.invokeMethod('setup', {'options': options});
final showAccountAlert = await _checkPhoneAccountPermission(
- options['additionalPermissions'] as List ?? []);
+ options['additionalPermissions'] as List);
final shouldOpenAccounts = await _alert(options, showAccountAlert);
if (shouldOpenAccounts) {
@@ -297,30 +315,38 @@ class FlutterCallkeep extends EventManager {
}
Future _checkPhoneAccountPermission(
- [List optionalPermissions]) async {
+ List? optionalPermissions) async {
if (!Platform.isAndroid) {
return true;
}
- return await _channel
+ var resp = await _channel
.invokeMethod('checkPhoneAccountPermission', {
- 'optionalPermissions': optionalPermissions ?? [],
+ 'optionalPermissions': optionalPermissions ?? [],
});
+ if (resp != null) {
+ return resp;
+ }
+ return false;
}
- Future _alert(Map options, bool condition) async {
+ Future _alert(Map options, bool? condition) async {
if (_context == null) {
return false;
}
- return await _showAlertDialog(
- _context,
+ var resp = await _showAlertDialog(
+ _context!,
options['alertTitle'] as String,
options['alertDescription'] as String,
options['cancelButton'] as String,
options['okButton'] as String);
+ if (resp != null) {
+ return resp;
+ }
+ return false;
}
- Future _showAlertDialog(BuildContext context, String alertTitle,
- String alertDescription, String cancelButton, String okButton) {
+ Future _showAlertDialog(BuildContext context, String? alertTitle,
+ String? alertDescription, String? cancelButton, String? okButton) {
return showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
@@ -328,15 +354,15 @@ class FlutterCallkeep extends EventManager {
content: Text(alertDescription ??
'This application needs to access your phone accounts'),
actions: [
- FlatButton(
- child: Text(cancelButton ?? 'Cancel'),
+ TextButton(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(false),
+ child: Text(cancelButton ?? 'Cancel'),
),
- FlatButton(
- child: Text(okButton ?? 'ok'),
+ TextButton(
onPressed: () =>
Navigator.of(context, rootNavigator: true).pop(true),
+ child: Text(okButton ?? 'ok'),
),
],
),
diff --git a/pubspec.yaml b/pubspec.yaml
index 4de89af2..97e2c5c2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -4,8 +4,8 @@ version: 0.2.4
homepage: https://github.com/flutter-webrtc/callkeep
environment:
- sdk: ">=2.2.2 <3.0.0"
- flutter: ^1.10.0
+ sdk: '>=2.12.0 <3.0.0'
+ flutter: '>=1.22.0'
dependencies:
flutter:
From 50652c4c66913877bec2cb5fd69b5ecc99a5b5be Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 12 Jun 2021 01:26:42 +0800
Subject: [PATCH 21/60] When using the alert format, print the log and
terminate the calkeep process.
---
ios/Classes/CallKeep.m | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index 498e3d7b..57d6785f 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -232,8 +232,14 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
"has_video": false,
}
*/
+
NSDictionary *dic = payload.dictionaryPayload;
+ if (dic[@"aps"] != nil) {
+ NSLog(@"Do not use the 'alert' format for push type %@.", payload.type);
+ return;
+ }
+
NSString *uuid = dic[@"uuid"];
NSString *callerId = dic[@"caller_id"];
NSString *callerName = dic[@"caller_name"];
From f49367b659cd00828091da8c0dcb4ab1746436ba Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 12 Jun 2021 07:41:09 +0800
Subject: [PATCH 22/60] Fixed receiving Voip push in background mode for iOS
13+.
---
ios/Classes/CallKeep.m | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/ios/Classes/CallKeep.m b/ios/Classes/CallKeep.m
index 57d6785f..9550dbd5 100644
--- a/ios/Classes/CallKeep.m
+++ b/ios/Classes/CallKeep.m
@@ -220,7 +220,8 @@ - (NSString *)createUUID {
return [uuidStr lowercaseString];
}
-- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
+
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(nonnull void (^)(void))completion {
// Process the received push
NSLog(@"didReceiveIncomingPushWithPayload payload = %@", payload.type);
/* payload example.
@@ -228,7 +229,7 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
"uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
"caller_id": "+8618612345678",
"caller_name": "hello",
- "caller_id_type": "number",
+ "caller_id_type": "number",
"has_video": false,
}
*/
@@ -237,6 +238,9 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
if (dic[@"aps"] != nil) {
NSLog(@"Do not use the 'alert' format for push type %@.", payload.type);
+ if(completion != nil) {
+ completion();
+ }
return;
}
@@ -260,7 +264,13 @@ - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayloa
localizedCallerName:callerName
fromPushKit:YES
payload:dic
- withCompletionHandler:^(){}];
+ withCompletionHandler:completion];
+}
+
+- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type {
+ [self pushRegistry:registry didReceiveIncomingPushWithPayload:payload forType:type withCompletionHandler:^(){
+ NSLog(@"[CallKeep] received");
+ }];
}
From 9b4d23e38db63efcbfa20b7f67f3fb2f29786fa7 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 12 Jun 2021 07:43:36 +0800
Subject: [PATCH 23/60] Update iOS project.
---
example/ios/Runner.xcodeproj/project.pbxproj | 23 ++++----------------
example/ios/Runner/Info.plist | 1 +
2 files changed, 5 insertions(+), 19 deletions(-)
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index f0934e16..1494cf9c 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 51;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
@@ -151,7 +151,6 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 55E51B0683F0D8A00CACA3AC /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -245,23 +244,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 55E51B0683F0D8A00CACA3AC /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -375,6 +357,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -508,6 +491,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -536,6 +520,7 @@
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 0d8a38cc..99939835 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -24,6 +24,7 @@
UIBackgroundModes
+ processing
remote-notification
voip
From 419ff9a2094181bd2fd2ae77d5f90849a6b1bfd3 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 12 Jun 2021 08:04:48 +0800
Subject: [PATCH 24/60] Add toolkit for fcm/apns push testing.
---
README.md | 4 +
tools/README.md | 17 ++
tools/cmd/main.go | 63 ++++
tools/go.mod | 13 +
tools/go.sum | 537 +++++++++++++++++++++++++++++++++++
tools/pkg/fcm/fcm.go | 36 +++
tools/pkg/pushkit/pushkit.go | 35 +++
7 files changed, 705 insertions(+)
create mode 100644 tools/README.md
create mode 100644 tools/cmd/main.go
create mode 100644 tools/go.mod
create mode 100644 tools/go.sum
create mode 100644 tools/pkg/fcm/fcm.go
create mode 100644 tools/pkg/pushkit/pushkit.go
diff --git a/README.md b/README.md
index 72c8a4d3..73c7b719 100644
--- a/README.md
+++ b/README.md
@@ -16,3 +16,7 @@
"has_video": false,
}
```
+
+## push test tool
+
+Please refer to the [Tool](/tools/) to test callkeep offline push.
diff --git a/tools/README.md b/tools/README.md
new file mode 100644
index 00000000..1ea2c3c3
--- /dev/null
+++ b/tools/README.md
@@ -0,0 +1,17 @@
+# CallKeep test tool
+
+This tool is used to demonstrate the basic push process for verifying callkeep. The tool is written in golang.
+
+## For iOS APNS
+
+please refer to `https://developer.apple.com/account/resources/certificates/add`.
+Choose `VoIP Services Certificate` to create a push certificate, download voip_services.cer and install it to the keychain tool, and export its private key rename it to `callkeep-apns.p12`
+
+`go run cmd/main.go -i +8618612345678 -p apns -d $ios_device_token`
+
+## For Android FCM
+
+please refer to `https://console.firebase.google.com/project/[your project]/settings/serviceaccounts/adminsdk`
+Select the `go` sdk format under your fcm project to download serviceAccountKey.json and rename it to `callkee-fcm.json`
+
+`go run cmd/main.go -i +8618612345678 -p fcm -d $android_fcm_token`
diff --git a/tools/cmd/main.go b/tools/cmd/main.go
new file mode 100644
index 00000000..ed5db82b
--- /dev/null
+++ b/tools/cmd/main.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+
+ "github.com/flutter-webrtc/callkeep/tools/pkg/fcm"
+ "github.com/flutter-webrtc/callkeep/tools/pkg/pushkit"
+ log "github.com/pion/ion-log"
+)
+
+func pushCallKeep(provider string, token string, payload map[string]string) error {
+ log.Infof("CallKeep Push Request")
+
+ log.Infof("provider=%v", provider)
+ log.Infof("token=%v", token)
+
+ data, _ := json.MarshalIndent(payload, "", " ")
+ log.Infof("payload=\n%v", string(data))
+
+ switch provider {
+ case "apns":
+ pushkit.APNSPush("./callkeep-apns.p12", token, payload)
+ return nil
+ case "fcm":
+ fcm.FCMPush("./callkeep-fcm.json", token, payload)
+ return nil
+ }
+ return fmt.Errorf("%v provider not found", provider)
+}
+
+func main() {
+ log.Init("info")
+ h := false
+ flag.BoolVar(&h, "h", false, "help")
+ provider := ""
+ flag.StringVar(&provider, "p", "fcm", "push provider type fcm | apns.")
+ token := ""
+ flag.StringVar(&token, "d", "", "device token.")
+ cid := ""
+ flag.StringVar(&cid, "i", "", "caller id.")
+ flag.Parse()
+
+ if h || len(token) == 0 || len(cid) == 0 {
+ flag.Usage()
+ return
+ }
+
+ log.Infof("try push")
+
+ payload := map[string]string{
+ "caller_id": cid,
+ "caller_name": "push test",
+ "caller_id_type": "number",
+ "has_video": "false",
+ }
+
+ err := pushCallKeep(provider, token, payload)
+ if err != nil {
+ log.Errorf("push failed %v", err)
+ }
+}
diff --git a/tools/go.mod b/tools/go.mod
new file mode 100644
index 00000000..b059a42d
--- /dev/null
+++ b/tools/go.mod
@@ -0,0 +1,13 @@
+module github.com/flutter-webrtc/callkeep/tools
+
+go 1.16
+
+require (
+ cloud.google.com/go/firestore v1.5.0 // indirect
+ firebase.google.com/go v3.13.0+incompatible
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
+ github.com/pion/ion-log v1.2.0
+ github.com/sideshow/apns2 v0.20.0
+ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
+ google.golang.org/api v0.48.0
+)
diff --git a/tools/go.sum b/tools/go.sum
new file mode 100644
index 00000000..99fcc1e5
--- /dev/null
+++ b/tools/go.sum
@@ -0,0 +1,537 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
+cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
+cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
+cloud.google.com/go v0.83.0 h1:bAMqZidYkmIsUqe6PtkEPT7Q+vfizScn+jfNA6jwK9c=
+cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.5.0 h1:4qNItsmc4GP6UOZPGemmHY4ZfPofVhcaKXsYw9wm9oA=
+cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
+firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
+github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/pion/ion-log v1.2.0 h1:FYJd+BWmg9FQYP729sgj9ctrr3KaLysnK2BILVpYL+Y=
+github.com/pion/ion-log v1.2.0/go.mod h1:oUlvCy7LZNPzOxmCZVraaMhcS/hB9XFog4m1A8QpVgM=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/sideshow/apns2 v0.20.0 h1:5Lzk4DUq+waVc6/BkKzpDTpQjtk/BZOP0YsayBpY1NE=
+github.com/sideshow/apns2 v0.20.0/go.mod h1:f7dArLPLbiZ3qPdzzrZXdCSlMp8FD0p6z7tHssDOLvk=
+github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
+go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
+golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644 h1:CA1DEQ4NdKphKeL70tvsWNdT5oFh1lOjihRcEDROi0I=
+golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
+google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
+google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
+google.golang.org/api v0.48.0 h1:RDAPWfNFY06dffEXfn7hZF5Fr1ZbnChzfQZAPyBd1+I=
+google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
+google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 h1:pc16UedxnxXXtGxHCSUhafAoVHQZ0yXl8ZelMH4EETc=
+google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/tools/pkg/fcm/fcm.go b/tools/pkg/fcm/fcm.go
new file mode 100644
index 00000000..f9aa06f0
--- /dev/null
+++ b/tools/pkg/fcm/fcm.go
@@ -0,0 +1,36 @@
+package fcm
+
+import (
+ "context"
+
+ firebase "firebase.google.com/go"
+ "firebase.google.com/go/messaging"
+ log "github.com/pion/ion-log"
+ "google.golang.org/api/option"
+)
+
+func FCMPush(fcmCert string, token string, payload map[string]string) (string, error) {
+ opt := option.WithCredentialsFile(fcmCert)
+ app, err := firebase.NewApp(context.Background(), nil, opt)
+
+ // Obtain a messaging.Client from the App.
+ ctx := context.Background()
+ client, err := app.Messaging(ctx)
+
+ // See documentation on defining a message payload.
+ message := &messaging.Message{
+ Data: payload,
+ Token: token,
+ }
+
+ // Send a message to the device corresponding to the provided
+ // registration token.
+ response, err := client.Send(ctx, message)
+ if err != nil {
+ log.Errorf("FCMPush: err %v", err)
+ return "", err
+ }
+ // Response is a message ID string.
+ log.Infof("Successfully sent message: %v", response)
+ return response, err
+}
diff --git a/tools/pkg/pushkit/pushkit.go b/tools/pkg/pushkit/pushkit.go
new file mode 100644
index 00000000..82e9211a
--- /dev/null
+++ b/tools/pkg/pushkit/pushkit.go
@@ -0,0 +1,35 @@
+package pushkit
+
+import (
+ "encoding/json"
+
+ log "github.com/pion/ion-log"
+ "github.com/sideshow/apns2"
+ "github.com/sideshow/apns2/certificate"
+)
+
+func APNSPush(p12 string, token string, payload map[string]string) {
+
+ cert, err := certificate.FromP12File(p12, "")
+ if err != nil {
+ log.Errorf("Cert Error: %v", err)
+ }
+
+ data, _ := json.Marshal(payload)
+ notification := &apns2.Notification{}
+ notification.DeviceToken = token
+ notification.PushType = "voip" // voip | alert
+ notification.Payload = []byte(data) // See Payload section below
+
+ // If you want to test push notifications for builds running directly from XCode (Development), use
+ client := apns2.NewClient(cert).Development()
+ // For apps published to the app store or installed as an ad-hoc distribution use Production()
+ //client := apns2.NewClient(cert).Production()
+ res, err := client.Push(notification)
+
+ if err != nil {
+ log.Errorf("Error: %v", err)
+ }
+
+ log.Infof("Push response: %v %v %v\n", res.StatusCode, res.ApnsID, res.Reason)
+}
From ea939ae3a288b7656b74442264b1afc91e6d3bff Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Sat, 12 Jun 2021 08:07:27 +0800
Subject: [PATCH 25/60] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 73c7b719..6f2fdbe7 100644
--- a/README.md
+++ b/README.md
@@ -19,4 +19,4 @@
## push test tool
-Please refer to the [Tool](/tools/) to test callkeep offline push.
+Please refer to the [Push Toolkit](/tools/) to test callkeep offline push.
From 248b9abe9987d7b9a314b218b34b9f9d7e95e23d Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Sat, 12 Jun 2021 08:10:19 +0800
Subject: [PATCH 26/60] Update README.md
---
tools/README.md | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/tools/README.md b/tools/README.md
index 1ea2c3c3..a2120455 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -2,16 +2,22 @@
This tool is used to demonstrate the basic push process for verifying callkeep. The tool is written in golang.
+prepare to run:
+
+`cd tools && go mod tidy`
+
## For iOS APNS
please refer to `https://developer.apple.com/account/resources/certificates/add`.
-Choose `VoIP Services Certificate` to create a push certificate, download voip_services.cer and install it to the keychain tool, and export its private key rename it to `callkeep-apns.p12`
+
+Choose `VoIP Services Certificate` to create a push certificate, download `voip_services.cer` and install it to the keychain tool, export its private key rename it to `callkeep-apns.p12`
`go run cmd/main.go -i +8618612345678 -p apns -d $ios_device_token`
## For Android FCM
please refer to `https://console.firebase.google.com/project/[your project]/settings/serviceaccounts/adminsdk`
-Select the `go` sdk format under your fcm project to download serviceAccountKey.json and rename it to `callkee-fcm.json`
+
+Select the `go` sdk format under your fcm project to download `serviceAccountKey.json` and rename it to `callkee-fcm.json`
`go run cmd/main.go -i +8618612345678 -p fcm -d $android_fcm_token`
From 726db974788f1b66d856f03dfca03525c26b606a Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Sat, 12 Jun 2021 08:14:48 +0800
Subject: [PATCH 27/60] Upgrade version to 0.3.0
---
CHANGELOG.md | 7 +++++++
pubspec.yaml | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8677e1b6..c5b58226 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
# Changelog
-----------------------------------------------
+[0.3.0] - 2021.06.12
+
+* null safety
+* Add toolkit for testing.
+* Fixed receiving Voip push in background mode for iOS 13+.
+* Fix crash when iOS push uses the wrong push format (alert).
+
[0.2.4] - 2021.01.08
* Fix crash when appName is not set.
diff --git a/pubspec.yaml b/pubspec.yaml
index 97e2c5c2..353a4a1a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: callkeep
description: iOS CallKit framework and Android ConnectionService for Flutter.
-version: 0.2.4
+version: 0.3.0
homepage: https://github.com/flutter-webrtc/callkeep
environment:
From e736cae61e9ed6f0e4318facc3855d7b8596b88f Mon Sep 17 00:00:00 2001
From: CloudWebRTC
Date: Sat, 12 Jun 2021 08:18:26 +0800
Subject: [PATCH 28/60] Update README.md
---
tools/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/README.md b/tools/README.md
index a2120455..cf0c9167 100644
--- a/tools/README.md
+++ b/tools/README.md
@@ -18,6 +18,6 @@ Choose `VoIP Services Certificate` to create a push certificate, download `voip_
please refer to `https://console.firebase.google.com/project/[your project]/settings/serviceaccounts/adminsdk`
-Select the `go` sdk format under your fcm project to download `serviceAccountKey.json` and rename it to `callkee-fcm.json`
+Select the `go` sdk format under your fcm project to download `serviceAccountKey.json` and rename it to `callkeep-fcm.json`
`go run cmd/main.go -i +8618612345678 -p fcm -d $android_fcm_token`
From 276f73f7bb8ef09ff3a823e8fcf6adb0952fef31 Mon Sep 17 00:00:00 2001
From: cloudwebrtc
Date: Wed, 16 Jun 2021 12:03:11 +0800
Subject: [PATCH 29/60] Fix that the call list cannot be displayed when the
push is received when andorid is running.
---
example/lib/main.dart | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 60f65981..7bad4677 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -324,9 +324,17 @@ class _MyAppState extends State {
print('onMessage: $message');
if (message.containsKey('data')) {
// Handle data message
- final dynamic data = message['data'];
- var number = data['body'] as String;
- await displayIncomingCall(number);
+ var payload = message['data'];
+ var callerId = payload['caller_id'] as String;
+ var callerName = payload['caller_name'] as String;
+ var uuid = payload['uuid'] as String;
+ var hasVideo = payload['has_video'] == "true";
+ final callUUID = uuid ?? Uuid().v4();
+ setState(() {
+ calls[callUUID] = Call(callerId);
+ });
+ _callKeep.displayIncomingCall(callUUID, callerId,
+ localizedCallerName: callerName, hasVideo: hasVideo);
}
},
onBackgroundMessage: myBackgroundMessageHandler,
From 395aaa3d1beefadd7f25effba1ad584cc4926ff0 Mon Sep 17 00:00:00 2001
From: Efra Espada
Date: Sun, 11 Jul 2021 10:14:40 +0200
Subject: [PATCH 30/60] feature: added some doc (#86)
* feature: added some doc [WIP]
* feature: added more doc
* feature: added more doc
* feature: added more doc on README.md
* feature: added more doc on README.md
* feature: doc added
* feature: doc added
* feature: doc added
* feature: doc added
---
README.md | 204 +++++++++++++++++++++++++++++++++++++++++++++-
images/sample.png | Bin 0 -> 1525100 bytes
2 files changed, 200 insertions(+), 4 deletions(-)
create mode 100644 images/sample.png
diff --git a/README.md b/README.md
index 6f2fdbe7..00a4685e 100644
--- a/README.md
+++ b/README.md
@@ -5,18 +5,214 @@
* iOS CallKit and Android ConnectionService for Flutter
* Support FCM and PushKit
-## push payload
+> Keep in mind Callkit is banned in China, so if you want your app in the chinese AppStore consider include a basic alternative for notifying calls (ex. FCM notifications with sound).
+
+`* P-C-M means -> presenter / controller / manager`
+
+## Introduction
+
+Callkeep acts as an intermediate between your call system (RTC, VOIP...) and the user, offering a native calling interface for handling your app calls.
+
+This allows you (for example) to answer calls when your device is locked even if your app is terminated.
+
+
+## Initial setup
+
+Basic configuration. In Android a popup is displayed before starting requesting some permissions to work properly.
+
+```dart
+final callSetup = {
+ 'ios': {
+ 'appName': 'CallKeepDemo',
+ },
+ 'android': {
+ 'alertTitle': 'Permissions required',
+ 'alertDescription':
+ 'This application needs to access your phone accounts',
+ 'cancelButton': 'Cancel',
+ 'okButton': 'ok',
+ },
+};
+
+callKeep.setup(callSetup);
+```
+
+This configuration should be defined when your application wakes up, but keep in mind this alert will appear if you aren't granting the needed permissions yet.
+
+A clean alternative is to control by yourself the required permissions when your application wakes up, and only invoke the `setup()` method if those permissions are granted.
+
+## Events
+
+Callkeep offers some events to handle native actions during a call.
+
+These events are quite crucial because they act as an intermediate between the native calling UI and your call P-C-M.
+
+What does it mean?
+
+Assuming your application already implements some calling system (RTC, Voip, or whatever) with its own calling UI, you are using some basic controls:
+
+
+
+> before implementing `callkeep`
+
+- Hang up -> `presenter.hangUp()`
+- Microphone switcher -> `presenter.microSwitch()`
+
+> after implementing `callkeep`
+
+- Hang up -> `callkeep.endCall(call_uuid)`
+- Microphone switcher -> `callKeep.setMutedCall(uuid, true / false)`
+
+Then you handle the action:
+
+```dart
+Function(CallKeepPerformAnswerCallAction) answerAction = (event) async {
+ print('CallKeepPerformAnswerCallAction ${event.callUUID}');
+ // notify to your call P-C-M the answer action
+};
+
+Function(CallKeepPerformEndCallAction) endAction = (event) async {
+ print('CallKeepPerformEndCallAction ${event.callUUID}');
+ // notify to your call P-C-M the end action
+};
+
+Function(CallKeepDidPerformSetMutedCallAction) setMuted = (event) async {
+ print('CallKeepDidPerformSetMutedCallAction ${event.callUUID}');
+ // notify to your call P-C-M the muted switch action
+};
+
+Function(CallKeepDidToggleHoldAction) onHold = (event) async {
+ print('CallKeepDidToggleHoldAction ${event.callUUID}');
+ // notify to your call P-C-M the hold switch action
+};
+```
+
+```dart
+callKeep.on(CallKeepDidToggleHoldAction(), onHold);
+callKeep.on(CallKeepPerformAnswerCallAction(), answerAction);
+callKeep.on(CallKeepPerformEndCallAction(), endAction);
+callKeep.on(CallKeepDidPerformSetMutedCallAction(), setMuted);
+```
+
+## Display incoming calls in foreground, background or terminate state
+
+The incoming call concept we are looking for is firing an incoming call action when "something" is received in our app.
+
+I've tested this concept with FCM and it works pretty fine.
+
+```dart
+final FlutterCallkeep _callKeep = FlutterCallkeep();
+bool _callKeepStarted = false;
+
+Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
+ await Firebase.initializeApp();
+ if (!_callKeepStarted) {
+ try {
+ await _callKeep.setup(callSetup);
+ _callKeepStarted = true;
+ } catch (e) {
+ print(e);
+ }
+ }
+
+ // then process your remote message looking for some call uuid
+ // and display any incoming call
+}
+```
+
+Displaying incoming calls is really simple if you are receiving FCM messages (or whatever). This example shows how to show and close any incoming call:
+
+> Notice that getting data from the payload can be done as you want, this is an example.
+
+A payload data example:
```json
{
"uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
- "caller_id": "+8618612345678",
- "caller_name": "hello",
+ "caller_id": "+0123456789",
+ "caller_name": "Draco",
"caller_id_type": "number",
- "has_video": false,
+ "has_video": "false"
}
```
+A `RemoteMessage` extension for getting data:
+
+```dart
+import 'dart:convert';
+
+import 'package:firebase_messaging/firebase_messaging.dart';
+
+extension RemoteMessageExt on RemoteMessage {
+ Map getContent() {
+ return jsonDecode(this.data["content"]);
+ }
+
+ Map payload() {
+ return getContent()["payload"];
+ }
+}
+```
+
+Methods to show and close incoming calls:
+
+```dart
+Future showIncomingCall(
+ BuildContext context,
+ RemoteMessage remoteMessage,
+ FlutterCallkeep callKeep,
+) async {
+ var callerIdFrom = remoteMessage.payload()["caller_id"] as String;
+ var callerName = remoteMessage.payload()["caller_name"] as String;
+ var uuid = remoteMessage.payload()["uuid"] as String;
+ var hasVideo = remoteMessage.payload()["has_video"] == "true";
+
+ callKeep.on(CallKeepDidToggleHoldAction(), onHold);
+ callKeep.on(CallKeepPerformAnswerCallAction(), answerAction);
+ callKeep.on(CallKeepPerformEndCallAction(), endAction);
+ callKeep.on(CallKeepDidPerformSetMutedCallAction(), setMuted);
+
+ print('backgroundMessage: displayIncomingCall ($uuid)');
+
+ bool hasPhoneAccount = await callKeep.hasPhoneAccount();
+ if (!hasPhoneAccount) {
+ hasPhoneAccount = await callKeep.hasDefaultPhoneAccount(context, callSetup["android"]);
+ }
+
+ if (!hasPhoneAccount) {
+ return;
+ }
+
+ await callKeep.displayIncomingCall(uuid, callerIdFrom, localizedCallerName: callerName, hasVideo: hasVideo);
+ callKeep.backToForeground();
+}
+
+Future closeIncomingCall(
+ RemoteMessage remoteMessage,
+ FlutterCallkeep callKeep,
+) async {
+ var uuid = remoteMessage.payload()[MessageManager.CALLER_UUID] as String;
+ print('backgroundMessage: closeIncomingCall ($uuid)');
+ bool hasPhoneAccount = await callKeep.hasPhoneAccount();
+ if (!hasPhoneAccount) {
+ return;
+ }
+ await callKeep.endAllCalls();
+}
+```
+
+### FAQ
+
+> I don't receive the incoming call
+
+Receiving incoming calls depends on FCM push messages (or the system you use) for handling the call information and displaying it.
+Remember FCM push messages not always works due to data-only messages are classified as "low priority". Devices can throttle and ignore these messages if your application is in the background, terminated, or a variety of other conditions such as low battery or currently high CPU usage. To help improve delivery, you can bump the priority of messages. Note; this does still not guarantee delivery. More info [here](https://firebase.flutter.dev/docs/messaging/usage/#low-priority-messages)
+
+> How can I manage the call if the app is terminated and the device is locked?
+
+Even in this scenario, the `backToForeground()` method will open the app and your call P-C-M will be able to work.
+
+
## push test tool
Please refer to the [Push Toolkit](/tools/) to test callkeep offline push.
diff --git a/images/sample.png b/images/sample.png
new file mode 100644
index 0000000000000000000000000000000000000000..de3f8b415254c1861d2f39cb794c791ca1e58e2f
GIT binary patch
literal 1525100
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