diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 00000000..5b928b1e
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,32 @@
+name: Publish plugin
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ - name: Install project dependencies
+ run: flutter pub get
+ - name: Dart Format Check
+ run: dart format lib/ test/ --set-exit-if-changed
+ - name: Import Sorter Check
+ run: flutter pub run import_sorter:main --no-comments --exit-if-changed
+ - name: Dart Analyze Check
+ run: flutter analyze
+ #- name: Check Publish Warnings
+ # run: dart pub publish --dry-run
+ - name: Publish
+ uses: k-paxian/dart-package-publisher@v1.5.1
+ with:
+ credentialJson: ${{ secrets.CREDENTIAL_JSON }}
+ flutter: true
+ skipTests: true
+ force: true
diff --git a/.github/workflows/pushMaster.yaml b/.github/workflows/pushMaster.yaml
new file mode 100644
index 00000000..fa25204c
--- /dev/null
+++ b/.github/workflows/pushMaster.yaml
@@ -0,0 +1,25 @@
+name: Push To Master
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ build:
+ name: Build Checks
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ channel: "stable"
+ - name: Install project dependencies
+ run: flutter pub get
+ - name: Dart Format Check
+ run: dart format lib/ test/ --set-exit-if-changed
+ - name: Import Sorter Check
+ run: flutter pub run import_sorter:main --no-comments --exit-if-changed
+ - name: Dart Analyze Check
+ run: flutter analyze
diff --git a/.gitignore b/.gitignore
index dbef116d..3b2be347 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,8 @@ doc/api/
*.js_
*.js.deps
*.js.map
+.DS_Store
+.idea
+.vscode
+android/gradle*
+example/ios/Podfile.lock
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5dfa8877..8c311edb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,64 @@
# Changelog
-----------------------------------------------
+
+[0.4.1] - 2025.02.03
+
+* [Fix] Updates firebase messaging and android gradle
+
+[0.4.0] - 2023.08.24
+
+* [fix] Extracting UI responsibility, named parameters, android dismissal fix (#189).
+* [fix] make the Object nullable (#182)
+* [chore] Updated to the latest flutter and firebase messaging (#185)
+* [feat] Improve Android broadcasts and iOS delegate (#159)
+
+[0.3.3] - 2023.01.25
+
+* [fix] Remove as `type` to allow null assignment.
+
+[0.3.2] - 2021.09.27
+
+* [feat] Add backgroundMode for setup.
+* [fix] Duplicated call onAnswer. New open method. (#111)
+* [fix] Cannot receive answer call on Android 11 (#98)
+
+[0.3.1] - 2021.07.27
+
+* Add foregroundService for Android 11.
+
+[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.
+* hasDefaultPhoneAccount give feedback about the user choice.
+
+[0.2.3] - 2021.01.08
+
+* Fix backToForeground method.
+
+[0.2.2] - 2020.12.27
+
+* Update json format for push payload.
+
+[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.
+* 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/README.md b/README.md
index 7daafac2..09bccce3 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,287 @@
# callkeep
-iOS CallKit and Android ConnectionService for Flutter
+
+[](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
+
+> 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
+callKeep.setup(
+ showAlertDialog: () async {
+ final BuildContext context = navigatorKey.currentContext!;
+
+ return await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('Permissions Required'),
+ content: const Text(
+ 'This application needs to access your phone accounts'),
+ actions: [
+ TextButton(
+ child: const Text('Cancel'),
+ onPressed: () => Navigator.of(context).pop(false),
+ ),
+ TextButton(
+ child: const Text('OK'),
+ onPressed: () => Navigator.of(context).pop(true),
+ ),
+ ],
+ );
+ },
+ ) ??
+ false;
+ },
+ options:{
+ 'ios': {
+ 'appName': 'CallKeepDemo',
+ },
+ 'android': {
+ 'additionalPermissions': [
+ 'android.permission.CALL_PHONE',
+ 'android.permission.READ_PHONE_NUMBERS'
+ ],
+ // Required to get audio in background when using Android 11
+ 'foregroundService': {
+ 'channelId': 'com.company.my',
+ 'channelName': 'Foreground service for my app',
+ 'notificationTitle': 'My app is running on background',
+ 'notificationIcon': 'mipmap/ic_notification_launcher',
+ },
+ },
+});
+```
+
+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
+Future answerCall(CallKeepPerformAnswerCallAction event) async {
+ print('CallKeepPerformAnswerCallAction ${event.callUUID}');
+ // notify to your call P-C-M the answer action
+};
+
+ Future endCall(CallKeepPerformEndCallAction event) async {
+ print('CallKeepPerformEndCallAction ${event.callUUID}');
+ // notify to your call P-C-M the end action
+};
+
+Future didPerformSetMutedCallAction(CallKeepDidPerformSetMutedCallAction event) async {
+ print('CallKeepDidPerformSetMutedCallAction ${event.callUUID}');
+ // notify to your call P-C-M the muted switch action
+};
+
+ Future didToggleHoldCallAction(CallKeepDidToggleHoldAction event) async {
+ print('CallKeepDidToggleHoldAction ${event.callUUID}');
+ // notify to your call P-C-M the hold switch action
+};
+```
+
+```dart
+
+ @override
+ void initState() {
+ super.initState();
+ callKeep.on(didDisplayIncomingCall);
+ callKeep.on(answerCall);
+ callKeep.on(endCall);
+ callKeep.on(didToggleHoldCallAction);
+ }
+```
+
+## 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": "+0123456789",
+ "caller_name": "Draco",
+ "caller_id_type": "number",
+ "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(onHold);
+ callKeep.on(answerAction);
+ callKeep.on(endAction);
+ callKeep.on(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();
+}
+```
+
+Pass in your own dialog UI for permissions alerts
+
+````dart
+showAlertDialog: () async {
+ final BuildContext context = navigatorKey.currentContext!;
+
+ return await showDialog(
+ context: context,
+ barrierDismissible: false,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('Permissions Required'),
+ content: const Text(
+ 'This application needs to access your phone accounts'),
+ actions: [
+ TextButton(
+ child: const Text('Cancel'),
+ onPressed: () => Navigator.of(context).pop(false),
+ ),
+ TextButton(
+ child: const Text('OK'),
+ onPressed: () => Navigator.of(context).pop(true),
+ ),
+ ],
+ );
+ },
+ ) ??
+ false;
+ },
+```
+
+
+
+
+### 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/analysis_options.yaml b/analysis_options.yaml
index b65afa88..6cb62a1d 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,6 +1,25 @@
-include: package:pedantic/analysis_options.yaml
+
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
rules:
- always_declare_return_types
- avoid_empty_else
@@ -30,7 +49,12 @@ linter:
- use_rethrow_when_possible
- valid_regexps
- void_checks
-
+
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
+
analyzer:
errors:
# treat missing required parameters as a warning (not a hint)
@@ -45,4 +69,3 @@ analyzer:
# Ignore analyzer hints for updating pubspecs when using Future or
# Stream and not importing dart:async
# Please see https://github.com/flutter/flutter/pull/24528 for details.
- sdk_version_async_exported_from_core: ignore
diff --git a/android/build.gradle b/android/build.gradle
index da4fba2a..6e991c4a 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -22,10 +22,11 @@ rootProject.allprojects {
apply plugin: 'com.android.library'
android {
- compileSdkVersion 28
+ compileSdkVersion 35
+ namespace 'com.github.cloudwebrtc.flutter_callkeep'
defaultConfig {
- minSdkVersion 23
+ minSdkVersion 21
}
lintOptions {
disable 'InvalidPackage'
@@ -36,10 +37,9 @@ android {
}
}
-
dependencies {
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
- implementation "com.android.support:support-core-utils:28.0.0"
+ implementation 'com.android.support:support-core-utils:28.0.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
}
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 01a286e9..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-5.6.2-all.zip
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index a2c1457a..8680616d 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
+
-
\ No newline at end of file
+
diff --git a/android/src/main/java/com/github/cloudwebrtc/flutter_callkeep/FlutterCallkeepPlugin.java b/android/src/main/java/com/github/cloudwebrtc/flutter_callkeep/FlutterCallkeepPlugin.java
index d9d5c2d3..8337f46d 100644
--- a/android/src/main/java/com/github/cloudwebrtc/flutter_callkeep/FlutterCallkeepPlugin.java
+++ b/android/src/main/java/com/github/cloudwebrtc/flutter_callkeep/FlutterCallkeepPlugin.java
@@ -12,39 +12,17 @@
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
-import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.wazo.callkeep.CallKeepModule;
/** FlutterCallkeepPlugin */
+/// The MethodChannel that will the communication between Flutter and native Android
+///
+/// This local reference serves to register the plugin with the Flutter Engine and unregister it
+/// when the Flutter Engine is detached from the Activity
public class FlutterCallkeepPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
- /// The MethodChannel that will the communication between Flutter and native Android
- ///
- /// This local reference serves to register the plugin with the Flutter Engine and unregister it
- /// when the Flutter Engine is detached from the Activity
private MethodChannel channel;
private CallKeepModule callKeep;
-
- /**
- * Plugin registration.
- */
- public static void registerWith(Registrar registrar) {
- final FlutterCallkeepPlugin plugin = new FlutterCallkeepPlugin();
-
- plugin.startListening(registrar.context(), registrar.messenger());
-
- if (registrar.activeContext() instanceof Activity) {
- plugin.setActivity((Activity) registrar.activeContext());
- }
-
- registrar.addViewDestroyListener(view -> {
- plugin.stopListening();
- return false;
- });
- }
-
- private void setActivity(@NonNull Activity activity) {
- callKeep.setActivity(activity);
- }
+ private Activity activity;
private void startListening(final Context context, BinaryMessenger messenger) {
channel = new MethodChannel(messenger, "FlutterCallKeep.Method");
@@ -53,10 +31,14 @@ private void startListening(final Context context, BinaryMessenger messenger) {
}
private void stopListening() {
- channel.setMethodCallHandler(null);
- channel = null;
- callKeep.dispose();
- callKeep = null;
+ if (channel != null) {
+ channel.setMethodCallHandler(null);
+ channel = null;
+ }
+ if (callKeep != null) {
+ callKeep.dispose();
+ callKeep = null;
+ }
}
@Override
@@ -66,7 +48,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
- if (!callKeep.HandleMethodCall(call, result)) {
+ if (!callKeep.handleMethodCall(call, result)) {
result.notImplemented();
}
}
@@ -78,21 +60,31 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
@Override
public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
- callKeep.setActivity(binding.getActivity());
+ activity = binding.getActivity();
+ if (callKeep != null) {
+ callKeep.setActivity(activity);
+ }
}
@Override
public void onDetachedFromActivityForConfigChanges() {
- callKeep.setActivity(null);
+ if (callKeep != null) {
+ callKeep.setActivity(null);
+ }
}
@Override
public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
- callKeep.setActivity(binding.getActivity());
+ activity = binding.getActivity();
+ if (callKeep != null) {
+ callKeep.setActivity(activity);
+ }
}
@Override
public void onDetachedFromActivity() {
- callKeep.setActivity(null);
+ if (callKeep != null) {
+ callKeep.setActivity(null);
+ }
}
}
diff --git a/android/src/main/java/io/wazo/callkeep/CallKeepBackgroundMessagingService.java b/android/src/main/java/io/wazo/callkeep/CallKeepBackgroundMessagingService.java
index 126c5e72..90b9e087 100644
--- a/android/src/main/java/io/wazo/callkeep/CallKeepBackgroundMessagingService.java
+++ b/android/src/main/java/io/wazo/callkeep/CallKeepBackgroundMessagingService.java
@@ -48,7 +48,9 @@ public static void acquireWakeLockNow(Context context) {
@Nullable
@Override
public IBinder onBind(Intent intent) {
- Log.d(TAG, "wakeUpApplication: " + intent.getStringExtra("callUUID") + ", number : " + intent.getStringExtra("handle") + ", displayName:" + intent.getStringExtra("name"));
+ Log.d(TAG, "wakeUpApplication: " + intent.getStringExtra(CallKeepConstants.EXTRA_CALL_UUID) +
+ ", number : " + intent.getStringExtra(CallKeepConstants.EXTRA_CALL_NUMBER) +
+ ", displayName:" + intent.getStringExtra(CallKeepConstants.EXTRA_CALLER_NAME));
//TODO: not implemented
return null;
}
diff --git a/android/src/main/java/io/wazo/callkeep/Constants.java b/android/src/main/java/io/wazo/callkeep/CallKeepConstants.java
similarity index 56%
rename from android/src/main/java/io/wazo/callkeep/Constants.java
rename to android/src/main/java/io/wazo/callkeep/CallKeepConstants.java
index 4310de56..8f90c4f3 100644
--- a/android/src/main/java/io/wazo/callkeep/Constants.java
+++ b/android/src/main/java/io/wazo/callkeep/CallKeepConstants.java
@@ -1,19 +1,34 @@
package io.wazo.callkeep;
-public class Constants {
+public class CallKeepConstants {
+ public static final String ACTION_WAKEUP_CALL = "ACTION_WAKEUP_CALL";
+
public static final String ACTION_ANSWER_CALL = "ACTION_ANSWER_CALL";
+ public static final String ACTION_REJECT_CALL = "ACTION_REJECT_CALL";
+ public static final String ACTION_INCOMING_CALL = "ACTION_INCOMING_CALL";
+ public static final String ACTION_FAILED_CALL = "ACTION_FAILED_CALL";
+ public static final String ACTION_ONGOING_CALL = "ACTION_ONGOING_CALL";
+
public static final String ACTION_AUDIO_SESSION = "ACTION_AUDIO_SESSION";
public static final String ACTION_CHECK_REACHABILITY = "ACTION_CHECK_REACHABILITY";
public static final String ACTION_DTMF_TONE = "ACTION_DTMF_TONE";
public static final String ACTION_END_CALL = "ACTION_END_CALL";
public static final String ACTION_HOLD_CALL = "ACTION_HOLD_CALL";
public static final String ACTION_MUTE_CALL = "ACTION_MUTE_CALL";
- public static final String ACTION_ONGOING_CALL = "ACTION_ONGOING_CALL";
+ public static final String ACTION_AUDIO_CALL = "ACTION_AUDIO_CALL";
public static final String ACTION_UNHOLD_CALL = "ACTION_UNHOLD_CALL";
public static final String ACTION_UNMUTE_CALL = "ACTION_UNMUTE_CALL";
public static final String ACTION_WAKE_APP = "ACTION_WAKE_APP";
public static final String EXTRA_CALL_NUMBER = "EXTRA_CALL_NUMBER";
public static final String EXTRA_CALL_UUID = "EXTRA_CALL_UUID";
+ public static final String EXTRA_CALL_DATA = "EXTRA_CALL_EXTRAS";
public static final String EXTRA_CALLER_NAME = "EXTRA_CALLER_NAME";
+ public static final String EXTRA_CALL_ATTRIB = "EXTRA_CALL_ATTRIB";
+
+
+ public static final String HOLD_SUPPORT_DATA_KEY = "io.wazo.callkeep.HoldSupported";
+ public static final String BROADCAST_RECEIVER_META_DATA_KEY = "io.wazo.callkeep.BroadcastReceiver";
+
+ public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128;
}
diff --git a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
index 6dc2ed92..3bb3ab03 100644
--- a/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
+++ b/android/src/main/java/io/wazo/callkeep/CallKeepModule.java
@@ -25,13 +25,13 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -40,92 +40,106 @@
import android.util.Log;
import android.view.WindowManager;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
-import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
-import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel.Result;
import io.wazo.callkeep.utils.Callback;
import io.wazo.callkeep.utils.ConstraintsMap;
import io.wazo.callkeep.utils.ConstraintsArray;
+import io.wazo.callkeep.utils.MapUtils;
import io.wazo.callkeep.utils.PermissionUtils;
-import static io.wazo.callkeep.Constants.*;
+import static io.wazo.callkeep.CallKeepConstants.*;
+
+import org.json.JSONException;
+import org.json.JSONObject;
// @see https://github.com/kbagchiGWC/voice-quickstart-android/blob/9a2aff7fbe0d0a5ae9457b48e9ad408740dfb968/exampleConnectionService/src/main/java/com/twilio/voice/examples/connectionservice/VoiceConnectionServiceActivity.java
public class CallKeepModule {
- public static final int REQUEST_READ_PHONE_STATE = 1337;
- public static final int REQUEST_REGISTER_CALL_PROVIDER = 394859;
-
private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
- private static final String REACT_NATIVE_MODULE_NAME = "CallKeep";
- private static final String[] permissions = { Manifest.permission.READ_PHONE_STATE,
- Manifest.permission.CALL_PHONE, Manifest.permission.RECORD_AUDIO };
-
+ private static final String E_CONNECTION_SERVICE_NOT_AVAILABLE = "E_CONNECTION_SERVICE_NOT_AVAILABLE";
private static final String TAG = "FLT:CallKeepModule";
+
private static TelecomManager telecomManager;
private static TelephonyManager telephonyManager;
- private static MethodChannel.Result hasPhoneAccountPromise;
- private Context _context;
- public static PhoneAccountHandle handle;
+ private static PhoneAccountHandle accountHandle;
+ private static ConstraintsMap settings;
+ private static boolean hasSetup = false;
+ private final Context context;
private boolean isReceiverRegistered = false;
private VoiceBroadcastReceiver voiceBroadcastReceiver;
- private ConstraintsMap _settings;
- Activity _currentActivity = null;
- MethodChannel _eventChannel;
+ private final List requiredPermissions = new LinkedList<>();
+ private Activity currentActivity = null;
+ private final MethodChannel eventChannel;
public CallKeepModule(Context context, BinaryMessenger messenger) {
- this._context = context;
- this._eventChannel = new MethodChannel(messenger, "FlutterCallKeep.Event");
+ this.context = context;
+ this.eventChannel = new MethodChannel(messenger, "FlutterCallKeep.Event");
+ }
+
+ public static PhoneAccountHandle getAccountHandle() {
+ return accountHandle;
}
public void setActivity(Activity activity) {
- this._currentActivity = activity;
+ this.currentActivity = activity;
}
- public void dispose(){
- LocalBroadcastManager.getInstance(this._context).unregisterReceiver(voiceBroadcastReceiver);
+ public void dispose() {
+ if (voiceBroadcastReceiver == null || this.context == null) return;
+ LocalBroadcastManager.getInstance(this.context).unregisterReceiver(voiceBroadcastReceiver);
VoiceConnectionService.setPhoneAccountHandle(null);
+ isReceiverRegistered = false;
}
- public boolean HandleMethodCall(@NonNull MethodCall call, @NonNull Result result) {
- switch(call.method) {
+ public boolean handleMethodCall(@NonNull MethodCall call, @NonNull Result result) {
+ switch (call.method) {
case "setup": {
- setup(new ConstraintsMap((Map)call.argument("options")));
+ setup(new ConstraintsMap(call.argument("options")));
result.success(null);
}
break;
case "displayIncomingCall": {
- displayIncomingCall((String)call.argument("uuid"), (String)call.argument("handle"), (String)call.argument("localizedCallerName"));
+ displayIncomingCallImpl(
+ call.argument("uuid"),
+ call.argument("handle"),
+ call.argument("callerName"),
+ call.argument("additionalData")
+ );
result.success(null);
}
break;
case "answerIncomingCall": {
- answerIncomingCall((String)call.argument("uuid"));
+ answerIncomingCall(call.argument("uuid"));
result.success(null);
}
break;
case "startCall": {
- startCall((String)call.argument("uuid"), (String)call.argument("number"), (String)call.argument("callerName"));
+ startCall(
+ call.argument("uuid"),
+ call.argument("handle"),
+ call.argument("callerName"),
+ call.argument("additionalData")
+ );
result.success(null);
}
break;
case "endCall": {
- endCall((String)call.argument("uuid"));
+ endCall(call.argument("uuid"));
result.success(null);
}
break;
@@ -134,8 +148,8 @@ public boolean HandleMethodCall(@NonNull MethodCall call, @NonNull Result result
result.success(null);
}
break;
- case "checkPhoneAccountPermission": {
- checkPhoneAccountPermission(new ConstraintsArray((ArrayList