1.1.36 • Published 12 months ago

ko-call v1.1.36

Weekly downloads
-
License
ISC
Repository
-
Last release
12 months ago

KO-CALL

npm package

ko-call

Installation

IOS

  1. Need to install these dependencies:

    yarn add ko-call react-native-agora@4.1.1 && react-native-keep-awake@4.0.0 && npx react-native link react-native-keep-awake && cd ios && pod install
  2. In AppDelegate.m add following lines:

...
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"edtalk"
                                            initialProperties:nil];
  
  //EKLENECEK OLAN BÖLÜM//
  //PushRegistry register işlemi yapılıyor
  self.voipQueue = dispatch_queue_create("com.edtalk.voipqueue", NULL);
  //self.pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  self.pushRegistry = [[PKPushRegistry alloc] initWithQueue: self.voipQueue];
  self.pushRegistry.delegate = self;
  self.pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
  //PushRegistry register işlemi yapılıyor
  
  
  [RNCallKeep setup:@{
    @"appName": @"ed-talk",
    @"maximumCallGroups": @3,
    @"maximumCallsPerCallGroup": @1,
    @"supportsVideo": @NO,
  }];
  //EKLENECEK OLAN BÖLÜM//
...
/* arama ve notifications metotları */

/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  // Register VoIP push token (a property of PKPushCredentials) with server
  NSLog(@"my console log");
  
  if([credentials.token length] == 0) {
  NSLog(@"voip token NULL");
  return;
  }
  
  const unsigned *tokenBytes = (const unsigned *)[credentials.token bytes];

  NSString *tkn = [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])];
  
  self.prefs = [NSUserDefaults standardUserDefaults];
  [self.prefs setObject:tkn forKey:@"deviceToken"];
  NSLog(@"TOKEN: %@", tkn);
  
}

//Bu metot olmak zorunda, o sebeple eklendi
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{

}
/* Cihaz tokeni burada alınıyor ve React tarafında da ulaşabilmek için
UserDefaults a kaydediliyor */

/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {

  //Eğer gelen bildirim voip bildirimi ise işlemleri yap
  if (type == PKPushTypeVoIP) {
    
    //UserDefault ile ReactNative tarafındaki Settings iletişimi sağlanıyor
    self.prefs = [NSUserDefaults standardUserDefaults];

    

    //Gelen verileri alıyoruz
    NSDictionary *payloadDict = payload.dictionaryPayload[@"data"];
    self.channel = payloadDict[@"channel"];
    self.token = payloadDict[@"token"];
    self.phoneNum = payloadDict[@"number"];
    self.callerName = payloadDict[@"teacher"];
    self.appId = payloadDict[@"appId"];
    
    //
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    self.remoteTimeStamp = [formatter numberFromString:payloadDict[@"callTime"]];
    NSDate *now = [NSDate date];
    self.localTimeStamp = @(round([now timeIntervalSince1970] * 1000));
    double timeDiffInMillis = [self.localTimeStamp doubleValue] - [self.remoteTimeStamp doubleValue];
    self.timeDiff = @(timeDiffInMillis / 1000.0);
    NSNumber *callTime = @28;
    NSLog(@"Time difference: %@", self.timeDiff);
    //UUID oluşturuluyor
    NSUUID * _Nonnull uuidTemp = [NSUUID UUID];
    self.uuid = uuidTemp;
    //react-native-callkeep paketi için uuid gönderilecek. String'e dönüştürülüyor
    NSString * _Nonnull uuidString = [[uuidTemp UUIDString] lowercaseString];
    [self.prefs setObject:uuidString forKey:@"uuid"];
    
    
    //Agora engine initialize işlemi yapılıyor
    [self initializeAgoraEngine];
    
    
    //Arama için gerekli configuration ayarları oluşturuluyor
    CXProviderConfiguration *providerConfiguration;
    providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"ed-talk"];
    
    providerConfiguration.supportsVideo = false;
    providerConfiguration.supportedHandleTypes = [[NSSet alloc] initWithObjects:[NSNumber numberWithInt:(int)CXHandleTypePhoneNumber], nil];
    providerConfiguration.maximumCallsPerCallGroup = 1;
    
    
    self.provider = [[CXProvider alloc] initWithConfiguration:providerConfiguration];
    [self.provider setDelegate:self queue:dispatch_get_main_queue()];
    //Arama için gerekli configuration ayarları oluşturuluyor
    
    //Aramayı raporlarmak için ön hazırlıklar yapılıyor
    CXCallUpdate * _Nonnull callUpdate = [[CXCallUpdate alloc] init];;
    CXHandle *phoneNumber = [[CXHandle alloc] initWithType:CXHandleTypePhoneNumber value: self.phoneNum];
    callUpdate.remoteHandle = phoneNumber;
    callUpdate.localizedCallerName = self.callerName;
    callUpdate.supportsDTMF = NO;
    callUpdate.supportsGrouping = NO;
    callUpdate.supportsHolding = NO;
    //Aramayı raporlarmak için ön hazırlıklar yapılıyor
    NSNumber *callDuration = @([callTime doubleValue] - [self.timeDiff doubleValue]);
    NSLog(@"Call duration: %@", callDuration);
    NSLog(@"Time difference is not greater than callTime");
    if ([self.timeDiff doubleValue] < [callTime doubleValue]) {
        // Do something if callTime is greater than time difference
        NSLog(@"Time difference is greater than callTime");
        //Arama yeni arama olarak CallKit'e raporlanıyor
        [self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
          if (error != nil) {
            NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
          }
          NSLog(@"timer comes here");          //25 Saniye süre için timer çağırılıyor
          [self startTimer:callDuration];
          completion();
        }];
        //Arama yeni arama olarak CallKit'e raporlanıyor
    } else {
        // Do something else if callTime is not greater than time difference
        NSLog(@"Time difference is not greater than callTime");
        [self.provider reportNewIncomingCallWithUUID:self.uuid update:callUpdate completion:^(NSError * _Nullable error) {
          if (error != nil) {
            NSLog(@"reportNewIncomingCall error: %@",error.localizedDescription);
          }
          NSLog(@"timer comes here");
          [self resetCallVariable];
          completion();
        }];
        [self endCall];
        //Missed call logic
    }
    completion();
  } else {
    NSLog(@"Gelen bildirim voip değillll");
  }
}

/* Gelen notification burada yakalanıyor ve işlemler burada yapılıyor */

//Timer durduruluyor ve saniye sıfırlanıyor
- (void) resetCallVariable {
  //Timerı durdur
  [self.timer invalidate];
  self.timer = nil;
  //Saniyeyi sıfırla
  self.second = 0;
}
- (void) startTimer:(NSNumber*)duration {
  self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
      NSInteger durationSeconds = [duration integerValue];
      self.second = self.second + 1;
      NSLog(@"%ld",(long)self.second);
      if(self.second >= durationSeconds) {
          [self resetCallVariable];
          [self endCall];
          [timer invalidate];
          self.timer = nil;
      }
  }];
}

//Agora initialize fonksiyonu
- (void)initializeAgoraEngine {
    self.agoraKit = [AgoraRtcEngineKit sharedEngineWithAppId: self.appId delegate:self];
    //Mikrofon ve aktif arama değişkenlerini false olarak işaretliyoruz
    self.isMuted = false;
    self.isActiveCall = false;
}

//Agora kanala giriş yapılıyor
- (void)joinChannel {
  //Ses ayarlarını yapıyoruz
  
    //Agora kanalına girmeden önce broadcaster olduğumuzu belirtiyoruz.
    AgoraRtcChannelMediaOptions *option = [[AgoraRtcChannelMediaOptions alloc] init];
    option.channelProfile =  AgoraChannelProfileLiveBroadcasting;
    option.clientRoleType = AgoraClientRoleBroadcaster;
  
    //Kanala yukarıdaki ayarlar ile giriyoruzz
    [self.agoraKit joinChannelByToken:self.token channelId:self.channel uid:0 mediaOptions:option joinSuccess:^(NSString * _Nonnull channel, NSUInteger uid, NSInteger elapsed) {
    }];
    
    NSInteger speakerResult = [self.agoraKit setDefaultAudioRouteToSpeakerphone:false];
    
    [self.agoraKit enableAudio];
  
    [self configureAudioSession];
    
    //Kanala giriş yapıldığı için aktif arama olduğunu belirtiyoruz
    //Eğer aktif arama varsa endCall metotunda agoradan da çıkılacak
    self.isActiveCall = true;

}

//Arama biterse yapılacak işlemler
-(void) endCall {
  [self.provider reportCallWithUUID: self.uuid endedAtDate:NSDate.date reason: CXCallEndedReasonRemoteEnded];
    //Eğer aktif arama varsa agora kanalından ayrıl ve engine destroy et
    if (self.isActiveCall) {
      [self.agoraKit leaveChannel:nil];
      self.isActiveCall = false;
    }
    [AgoraRtcEngineKit destroy];
}

//Aramaya yanıt verildiğinde yapılacak işlemler
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
    //Bütün değerler sıfırlanıyor
    [self resetCallVariable];
    //Gelen token ve kanal bilgileri ile kanala giriliyor
    [self joinChannel];
  
    [action fulfill];
}

//Arama reddedildiğinde yapılan işlemler
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
    //Arama kapandığında uuid değişeknini sıfırla
    self.prefs = [NSUserDefaults standardUserDefaults];
    [self.prefs setObject:@"" forKey:@"uuid"];
  
    //Bütün değerler sıfırlanıyor
    [self resetCallVariable];
    //Arama kapatılıyor
    [self endCall];
    [action fulfill];
}

//Mikrofon kapatıldığında yapılan işlemler
- (void)provider:(CXProvider *)provider performSetMutedCallAction:(nonnull CXSetMutedCallAction *)action {
    [self.agoraKit muteLocalAudioStream:!self.isMuted];
    self.isMuted = !self.isMuted;
    [action fulfill];
}

//Cihaz kilitliyken arama gelirse mikrofonu etkinleştiriyoruz
- (void) configureAudioSession {
  [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeDefault options: AVAudioSessionCategoryOptionDuckOthers | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP error:nil];
  
  [[AVAudioSession sharedInstance] setMode:AVAudioSessionModeVoiceChat error:nil];
  
}

//Eğer karşı taraf aramayı kapatırsa
- (void) rtcEngine:(AgoraRtcEngineKit *)engine didOfflineOfUid:(NSUInteger)uid reason:(AgoraUserOfflineReason)reason {
  [self endCall];
}
  1. in AppDelegate.h file:
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <AgoraRtcKit/AgoraRtcEngineKit.h>
#import <CallKit/CallKit.h>
#import <PushKit/PushKit.h>
#import <AVFAudio/AVAudioSession.h>
#import <RNCallkeep/RNCallKeep.h>

//PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate isimli delegeler ekleniyor
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, PKPushRegistryDelegate, CXProviderDelegate, AgoraRtcEngineDelegate>

@property (nonatomic, strong) UIWindow *window;

//UserDefaults için genel değişken tanımlanıyor
@property NSUserDefaults *prefs;

//Agora için gerekli olan veriler buraya tanımlanıyor
@property (strong, nonatomic) AgoraRtcEngineKit *agoraKit;
@property NSString *channel;
@property NSString *token;
@property NSString *phoneNum;
@property NSString *callerName;
@property NSString *appId;
@property NSNumber *remoteTimeStamp;
@property NSNumber *localTimeStamp;
@property NSNumber *timeDiff;
@property Boolean isActiveCall;
@property Boolean isMuted;

//Arama için gerekli değişkenler
@property CXProvider *provider;
@property PKPushRegistry *pushRegistry;
@property NSUUID *uuid;
@property NSObject<OS_dispatch_queue> *voipQueue;

//Aramanın 25 saniye sürüp kapanması için gerekli değişkenler
@property NSInteger second;
@property NSTimer *timer;

@end
  1. in info.plist:
  	<key>NSCameraUsageDescription</key>
	<string>The app needs camera permission to attend the classes.</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>The app needs microphone permission to attend the classes.</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>remote-notification</string>
		<string>voip</string>
	</array>
  1. in the podfile add this lines:
  pod 'RNCallKeep', :path => '../node_modules/react-native-callkeep'
  1. Enable Push Notifications

  2. Enable Background Modes

ANDROID

  1. in AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.edtalk">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />    
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
    <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:usesCleartextTraffic="true"
      android:theme="@style/AppTheme">

      <activity
        android:name=".MainActivity"
        android:resizeableActivity="true"
        android:supportsPictureInPicture="true"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|smallestScreenSize|orientation"
        android:windowSoftInputMode="adjustResize"
        android:exported="true"
        android:launchMode="singleTask"
      >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
            <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
        </intent-filter>
        <intent-filter>
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
          <category android:name="android.intent.category.BROWSABLE" />
          <data android:scheme="edtalk" />
        </intent-filter>
      </activity>
      <receiver android:name=".ActionReceiver" />
    </application>
</manifest>
  1. in android build.gradle:
buildscript {
    ext {
        buildToolsVersion = "30.0.2"
        minSdkVersion = 26
        compileSdkVersion = 31
        targetSdkVersion = 31
        ndkVersion = "21.4.7075529"
    }
    ...
}
  1. in android/app/src/main/java/com/edtalk we need 3 files, first one is ActionReceiver.java:
package com.edtalk;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Vibrator;

public class ActionReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String[] actionArray=intent.getStringExtra("action").split(":");
        String action = actionArray[0];
        String notificationId = actionArray[1];

        if (action.equals("Reject")) {
            Vibrator rr = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
            KoCall.ringToneStop();
            rr.cancel();
            KoCall.removeNotification(notificationId);
            KoCall.resetCallVariables();
        }
    }
}

second one is KoCall.java:

package com.edtalk;

import static android.content.Context.NOTIFICATION_SERVICE;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.Rational;

import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import java.util.Calendar;
import java.util.concurrent.TimeUnit;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.storage.ReactDatabaseSupplier;
import com.reactnativecommunity.asyncstorage.AsyncLocalStorageUtil;
import com.zxcpoiu.incallmanager.InCallManagerModule;


public class KoCall extends ReactContextBaseJavaModule {
    private static ReactApplicationContext reactContext;
    public static final String LOG_TAG = "KO:KoCall";

    //React native tarafında iletişide kullanılacak değişkenler
    public static int notificationId;
    private String teacher;
    //25 saniyelik süreyi korumak için kullanılacak değişkenler
    private long firstTime;
    private long currentTime;

    //Uygulamanın deeplink ismi bu alanda tutuluyor.
    //Her uygulama için bu alan değişecek.
    private final String deepLink = "edtalk";
    //InCallManager react-native tarafında başladığında native tarafta durmuyor
    //O sebeple başlatma ve durdurma işlemlerini native tarafta ara metot ile yapıyoruz.
    private static InCallManagerModule inCallManager;
    //AsyncStorage da bazı değerleri native tarafta güncellememiz gerekiyor.
    //O sebeple bu değişkenleri tutuyoruz
    private static ReactDatabaseSupplier reactDatabaseSupplier;
    static final String TABLE_CATALYST = "catalystLocalStorage";
    static final String KEY_COLUMN = "key";
    static final String VALUE_COLUMN = "value";

    //Picture-inPicture için değişkenleri tanımlıyoruz
    private static final int ASPECT_WIDTH = 16;
    private static final int ASPECT_HEIGHT = 9;
    private static boolean isPipSupported = false;
    private static boolean isCustomAspectRatioSupported = false;
    private static Rational aspectRatio;
    //Picture-inPicture için değişkenleri tanımlıyoruz


    public KoCall(ReactApplicationContext context) {
        super(context);
        reactContext = context;

        inCallManager = new InCallManagerModule(reactContext);

        //Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            isPipSupported = true;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            isCustomAspectRatioSupported = true;
            aspectRatio = new Rational(ASPECT_WIDTH, ASPECT_HEIGHT);
        }
        //Sürüme göre Pictur-inPicture desteklenme işlerini değişkenlere atıyoruz
    }

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

    //Uygulama arka plandayken arama gelirse notification çıkması için bu metot kullanılıyor
    @SuppressLint("NotificationTrampoline")
    @ReactMethod
    private void showCallNotification(String teacher) {
        this.notificationId = (int) SystemClock.uptimeMillis();
        String channelId = "fcm_call_channel";
        String channelName = "Incoming Call";

        if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
            this.teacher = teacher;
        } else {
            this.teacher = "Teacher";
        }

        //Aktif arama başlangıçta true ya çekiliyor.
        //Bu sayede uygulama açılınca kanala otomarik giriş sağlanacak
        setAsyncStorageItem("activeCall", "true");

        String notification_title = this.teacher + " is calling";

        String packageName = reactContext.getPackageName();
        Intent launchIntent = reactContext.getPackageManager().getLaunchIntentForPackage(packageName);
        String className = launchIntent.getComponent().getClassName();
        Class<?> activityClass;

        try {
            activityClass = Class.forName(className);
        } catch (Exception e) {
            return;
        }

        //NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER
        //Open Call butonuna bastığımızda uygulamada arama ekranının açılması gerekiyor.
        //Bu sebeple deeplink kullanıyoruz ve call ekranına notification Id gönderiyoruz
        String openCallUri = (new StringBuilder()).append(deepLink).append("://home/").toString();
        Intent openCallIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(openCallUri), reactContext, activityClass);
        openCallIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        //Reject e basıldığında BroadcastRecevier ile bu durumu yakalayarak uygulamayı açmadan sesi kapatıyoruz
        //AsyncStorage ile bazı verileri güncelliyoruz. Bize ActionReceiverClass bu konuda yardımcı oluyor
        Intent rejectIntent = new Intent(reactContext, ActionReceiver.class);
        rejectIntent.putExtra("action", "Reject:" + notificationId);
        //NOTIFICATION BUTONLARI VE BUTONLARA BASILDIĞINDA YAPILACAK İŞLEMLER

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
                .setContentTitle(notification_title)
                .setContentText(channelName)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setCategory(NotificationCompat.CATEGORY_CALL)
                .setAutoCancel(true)
                .addAction(0, "Accept", PendingIntent.getActivity(reactContext, 0, openCallIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
                .addAction(0, "Reject", PendingIntent.getBroadcast(reactContext, 1, rejectIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT))
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setSmallIcon(R.mipmap.ic_launcher);


        NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        int importance = NotificationManager.IMPORTANCE_HIGH;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

            NotificationChannel mChannel = new NotificationChannel(
                    channelId, channelName, importance);
            mChannel.setDescription(channelName);
            mChannel.enableLights(true);
            mChannel.enableVibration(true);
            notificationManager.createNotificationChannel(mChannel);
        }

        notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
        notificationManager.notify(notificationId, notificationBuilder.build());
        firstTime = Calendar.getInstance().getTimeInMillis();

    }

    //Eğer aramaya yanıt verilmezse missed call notification düşüyor
    @ReactMethod
    private void showMissedCallNotification(String teacher) {
        int _notificationId = (int) SystemClock.uptimeMillis();
        String channelId = "fcm_missed_call_channel";
        String channelName = "Missed Call";

        // 25 saniye sonra sesi durdur
        // Programda kapanıyor ama herhangi bir aksilik olursa sesi ve titreşimi kapatmamız iyi olur
        ringToneStop();
        Vibrator rr = (Vibrator) reactContext.getSystemService(Context.VIBRATOR_SERVICE);
        rr.cancel();
        //Aktif arama başlangıçta true ya çekiliyor.
        //Eğer arama karşılanmazsa bu alan boş olarak güncellenecek
        setAsyncStorageItem("activeCall", "");
        String _teacher = "";
        if (teacher != null && !teacher.isEmpty() && !teacher.equals("")) {
            _teacher = teacher;
        } else {
            _teacher = "Teacher";
        }

        String notification_title = "Missed call - " + _teacher;
        //Missedcall notificationa basıldığında uygulamamızın açılması için bu bölümü oluşturuyoruz.
        //setContentIntent ile Notification a burayı ekleyeceğiz
        Intent intent = new Intent(reactContext, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(reactContext, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT);

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(reactContext, channelId)
                .setContentTitle(notification_title)
                .setContentText(channelName)
                .setPriority(NotificationCompat.PRIORITY_MAX)
                .setCategory(NotificationCompat.CATEGORY_CALL)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
                .setDefaults(Notification.DEFAULT_VIBRATE)
                .setSmallIcon(R.mipmap.ic_launcher);

        NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        int importance = NotificationManager.IMPORTANCE_HIGH;

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {

            NotificationChannel mChannel = new NotificationChannel(
                    channelId, channelName, importance);
            mChannel.setDescription(channelName);
            mChannel.enableLights(true);
            mChannel.enableVibration(true);
            notificationManager.createNotificationChannel(mChannel);
        }

        notificationBuilder.build().flags |= Notification.FLAG_AUTO_CANCEL;
        notificationManager.notify(_notificationId, notificationBuilder.build());

    }

    //Eğer uygulama arka planda iken arama gelirse counter bu metot aracılığı ile
    //uygulamaya aktarılıyor. Buradaki amaç 25 saniye çalma süresini korumak
    @ReactMethod
    public void getCounter(Callback callback) {
        currentTime = Calendar.getInstance().getTimeInMillis();
        long counter = TimeUnit.MILLISECONDS.toSeconds(currentTime - firstTime);

        callback.invoke(String.valueOf(counter));
    }

    //Aramaya ait notificationId bu bölümden gidiyor
    @ReactMethod
    public void getNotificationId(Callback callback) {
        callback.invoke(notificationId);
    }

    //Arama notification silinmesi için bu metot kullanılyor.
    @ReactMethod
    public void removeCallNotification(String notificationId) {
        this.notificationId = 0;
        removeNotification(notificationId);
    }

    //Notification alma bölümünün aktif olup olmadığı kontrol ediliyor
    //Eğer bu metot false dönerse react-native tarafında Linking.openSettings()
    //ile uygulama ayarlarını açtıracağız
    @ReactMethod
    public void allowNotification(final Promise promise) {
        try {
            Boolean areEnabled = NotificationManagerCompat.from(reactContext).areNotificationsEnabled();
            promise.resolve(areEnabled);
        } catch (Exception e) {
            promise.reject("Error", e.getMessage());
        }
    }

    //IncallManager react-native tarafında başladığında native tarafta durmuyor
    //Bu sebeple başlatma işlemini ara bir metot ile yapıyoruz
    @ReactMethod
    public void startRingTone(int seconds) {
        inCallManager.startRingtone("_DEFAULT_", seconds);
    }

    //IncallManager react-native tarafında başladığında native tarafta durmuyor
    //Bu sebeple durdurma işlemini ara bir metot ile yapıyoruz
    @ReactMethod
    public void stopRingTone() {
        ringToneStop();
    }

    //Notification Id 0 lanıyor
    @ReactMethod
    public void resetNotificationId() {
        notificationId = 0;
    }

    //Picture-in-Picture için kullanılan metotlar burada yer alıyor

    @ReactMethod
    public void enterPictureInPictureMode() {
        enterPipMode();
    }
    //Picture-in-Picture için kullanılan metotlar burada yer alıyor

    //Eğer battery optimizasyonu kısıtlanmış olarak işaretli ise arka plan bildirimleri gelmiyor
    //Burada onun kontrolünü yapacağız
    @ReactMethod
    public void isBatteryOptEnabled(Promise promise) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            String packageName = reactContext.getPackageName();
            ActivityManager activityManager = (ActivityManager) reactContext.getSystemService(Context.ACTIVITY_SERVICE);
            Boolean isRestricted = activityManager.isBackgroundRestricted();

            if (!isRestricted) {
                promise.resolve(true);
                return;
            }
        }
        promise.resolve(false);
    }

    //AsyncStorage DB sinden veri okuyabilmemiz için bu metotu kullanacağız
    public static String getAsyncStorageItem(String key) {
        String result = "";
        SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();

        if (db != null) {
            result = AsyncLocalStorageUtil.getItemImpl(db, key);
        }

        return result;
    }

    //AsyncStorage DB sinde güncelleme yapabilmemiz için bu metotu kullanacağız
    public static boolean setAsyncStorageItem(String key, String value) {
        SQLiteDatabase db = reactDatabaseSupplier.getInstance(reactContext).getReadableDatabase();
        ContentValues contentValues = new ContentValues();
        contentValues.put(KEY_COLUMN, key);
        contentValues.put(VALUE_COLUMN, value);

        long inserted = db.insertWithOnConflict(
                TABLE_CATALYST,
                null,
                contentValues,
                SQLiteDatabase.CONFLICT_REPLACE);
        db.close();
        return (-1 != inserted);
    }

    //Pip mode diğer class içinde de kullanabilmek için static bir metota taşıyoruz
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void enterPipMode() {
        if (isPipSupported) {
            if (isCustomAspectRatioSupported) {
                PictureInPictureParams params = new PictureInPictureParams.Builder().setAspectRatio(aspectRatio).build();
                reactContext.getCurrentActivity().enterPictureInPictureMode(params);
            } else
                reactContext.getCurrentActivity().enterPictureInPictureMode();
        }
    }

    //InCallManager durdurma işlemi
    public static void ringToneStop() {
        inCallManager.stopRingtone();
    }
    //InCallManager durdurma işlemi

    //Notification silme işlemi. Birden fazla yerde kullanıldığı için tekrara düşmemek adına buraya alındı
    public static void removeNotification(String notificationId) {
        int _notificationId = Integer.parseInt(notificationId);
        NotificationManager manager = (NotificationManager) reactContext.getSystemService(NOTIFICATION_SERVICE);
        manager.cancel(_notificationId);
    }

    //Arama reddedildiğinde AsyncStorage da bazı alanları güncellememiz gerekiyor
    public static void resetCallVariables() {

        //Aramaya ait verileri temizliyoruz
        setAsyncStorageItem("incomingCall", "");
        setAsyncStorageItem("activeCall", "");
        setAsyncStorageItem("appId", "");
        setAsyncStorageItem("token", "");
        setAsyncStorageItem("channelName", "");
        setAsyncStorageItem("teacherName", "");
        //setAsyncStorageItem("teacherImg", "");
        setAsyncStorageItem("providerType", "");
        //Arama reddedildiği için notification Id yi sıfırlıyor ve incomingCall u false a çekiyoruz
        notificationId = 0;
    }
}

and the finally last one is KoCallPackage.java:

package com.edtalk;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

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

public class KoCallPackage  implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Arrays.<NativeModule>asList(new KoCall(reactContext));
    }

    // Deprecated from RN 0.47
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

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

TEST CASES

library check list

1.1.36

12 months ago

1.1.35

12 months ago

1.1.34

12 months ago

1.1.33

1 year ago

1.1.32

1 year ago

1.1.31

1 year ago

1.1.29

1 year ago

1.1.30

1 year ago

1.1.28

1 year ago

1.1.12

1 year ago

1.1.16

1 year ago

1.1.15

1 year ago

1.1.14

1 year ago

1.1.13

1 year ago

1.1.19

1 year ago

1.1.18

1 year ago

1.1.17

1 year ago

1.1.23

1 year ago

1.1.22

1 year ago

1.1.21

1 year ago

1.1.20

1 year ago

1.1.27

1 year ago

1.1.26

1 year ago

1.1.25

1 year ago

1.1.24

1 year ago

1.1.11

2 years ago

1.1.10

2 years ago

1.1.9

2 years ago

1.1.8

2 years ago

1.1.7

2 years ago

1.1.6

2 years ago

1.1.5

2 years ago

1.1.4

2 years ago

1.1.3

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago