edoctor-sendbird-calls 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +33 -0
- package/android/build.gradle +82 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +34 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/ComingCallActivity.java +41 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/RNSendBirdCallsModule.java +2068 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/RNSendBirdCallsPackage.java +32 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/RNVSendBirdCallsVideo.java +10 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/RNVSendBirdCallsVideoManager.java +77 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/RNVideoViewGroup.java +109 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/SharedUtils.java +127 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/VcCallReceiver.java +37 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/VcHeadlessService.java +23 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/VoIPBaseService.java +100 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/VoIPService.java +164 -0
- package/android/src/main/java/com/edoctorsendbirdcalls/XiaomiUtilities.java +75 -0
- package/android/src/main/res/drawable/bar_selector_white.xml +18 -0
- package/android/src/main/res/drawable/button_answer.xml +8 -0
- package/android/src/main/res/drawable/button_reject.xml +8 -0
- package/android/src/main/res/drawable/call_notification_line.xml +8 -0
- package/android/src/main/res/drawable/ic_answer.xml +5 -0
- package/android/src/main/res/drawable/ic_call_notification_answer.xml +6 -0
- package/android/src/main/res/drawable/ic_call_notification_decline.xml +9 -0
- package/android/src/main/res/drawable/ic_reject.xml +5 -0
- package/android/src/main/res/drawable/rn_edit_text_material.xml +36 -0
- package/android/src/main/res/layout/call_notification.xml +124 -0
- package/edoctor-sendbird-calls.podspec +27 -0
- package/ios/CXCallManager.swift +211 -0
- package/ios/CXProvider.swift +38 -0
- package/ios/Extensions/UIKit/UIColor.swift +30 -0
- package/ios/Extensions/UIKit/UIView+Extension.swift +61 -0
- package/ios/Extensions/UIKit/UIViewController.swift +28 -0
- package/ios/PrivacyInfo.xcprivacy +47 -0
- package/ios/RNSendBirdCalls-Bridging-Header.h +3 -0
- package/ios/RNSendBirdCalls.mm +94 -0
- package/ios/RNSendBirdCalls.swift +549 -0
- package/ios/RNSendBirdCallsHelper.swift +28 -0
- package/ios/RNVSendBirdCallsVideo.swift +55 -0
- package/ios/RNVSendBirdCallsVideoManager.mm +6 -0
- package/ios/RNVSendBirdCallsVideoManager.swift +15 -0
- package/ios/SendBirdCallsExtentions.swift +46 -0
- package/lib/module/SendBirdCalls.js +377 -0
- package/lib/module/SendBirdCalls.js.map +1 -0
- package/lib/module/SendBirdCallsEvents.js +108 -0
- package/lib/module/SendBirdCallsEvents.js.map +1 -0
- package/lib/module/SendBirdCallsVideo.js +33 -0
- package/lib/module/SendBirdCallsVideo.js.map +1 -0
- package/lib/module/index.js +14 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/index.type.js +2 -0
- package/lib/module/index.type.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/SendBirdCalls.d.ts +63 -0
- package/lib/typescript/src/SendBirdCalls.d.ts.map +1 -0
- package/lib/typescript/src/SendBirdCallsEvents.d.ts +36 -0
- package/lib/typescript/src/SendBirdCallsEvents.d.ts.map +1 -0
- package/lib/typescript/src/SendBirdCallsVideo.d.ts +18 -0
- package/lib/typescript/src/SendBirdCallsVideo.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/index.type.d.ts +14 -0
- package/lib/typescript/src/index.type.d.ts.map +1 -0
- package/package.json +154 -0
- package/src/SendBirdCalls.ts +472 -0
- package/src/SendBirdCallsEvents.ts +141 -0
- package/src/SendBirdCallsVideo.tsx +44 -0
- package/src/index.tsx +11 -0
- package/src/index.type.ts +20 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.ReactPackage;
|
|
6
|
+
import com.facebook.react.bridge.NativeModule;
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager;
|
|
9
|
+
|
|
10
|
+
import java.util.ArrayList;
|
|
11
|
+
import java.util.Collections;
|
|
12
|
+
import java.util.List;
|
|
13
|
+
|
|
14
|
+
public class RNSendBirdCallsPackage implements ReactPackage {
|
|
15
|
+
|
|
16
|
+
@NonNull
|
|
17
|
+
@Override
|
|
18
|
+
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
|
|
19
|
+
List<NativeModule> modules = new ArrayList<>();
|
|
20
|
+
modules.add(new RNSendBirdCallsModule(reactContext));
|
|
21
|
+
return modules;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@NonNull
|
|
25
|
+
@Override
|
|
26
|
+
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
|
27
|
+
List<ViewManager> viewManagers = new ArrayList<>();
|
|
28
|
+
// Uncomment if you have video view manager
|
|
29
|
+
viewManagers.add(new RNVSendBirdCallsVideoManager());
|
|
30
|
+
return viewManagers;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
4
|
+
|
|
5
|
+
public class RNVSendBirdCallsVideo extends RNVideoViewGroup {
|
|
6
|
+
|
|
7
|
+
public RNVSendBirdCallsVideo(ThemedReactContext themedReactContext) {
|
|
8
|
+
super(themedReactContext);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.view.View;
|
|
4
|
+
|
|
5
|
+
import androidx.annotation.NonNull;
|
|
6
|
+
import androidx.annotation.Nullable;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
9
|
+
import com.facebook.react.uimanager.ViewGroupManager;
|
|
10
|
+
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
11
|
+
import com.sendbird.calls.DirectCall;
|
|
12
|
+
import com.sendbird.calls.SendBirdCall;
|
|
13
|
+
import com.sendbird.calls.SendBirdVideoView;
|
|
14
|
+
|
|
15
|
+
import org.jetbrains.annotations.NotNull;
|
|
16
|
+
import org.webrtc.RendererCommon;
|
|
17
|
+
|
|
18
|
+
public class RNVSendBirdCallsVideoManager extends ViewGroupManager<RNVSendBirdCallsVideo> {
|
|
19
|
+
|
|
20
|
+
private int propWidth;
|
|
21
|
+
private int propHeight;
|
|
22
|
+
private String curCallId;
|
|
23
|
+
private DirectCall directCall;
|
|
24
|
+
|
|
25
|
+
@NotNull
|
|
26
|
+
@Override
|
|
27
|
+
public String getName() {
|
|
28
|
+
return "RNVSendBirdCallsVideo";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@NonNull
|
|
32
|
+
@NotNull
|
|
33
|
+
@Override
|
|
34
|
+
protected RNVSendBirdCallsVideo createViewInstance(@NonNull @NotNull ThemedReactContext reactContext) {
|
|
35
|
+
return new RNVSendBirdCallsVideo(reactContext);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@ReactProp(name = "callId")
|
|
39
|
+
public void callId(RNVSendBirdCallsVideo view, @Nullable String callId) {
|
|
40
|
+
if(callId.length() > 0) {
|
|
41
|
+
curCallId = callId;
|
|
42
|
+
directCall = SendBirdCall.getCall(callId);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@ReactProp(name = "local", defaultBoolean = false)
|
|
47
|
+
public void local(RNVSendBirdCallsVideo view, @Nullable Boolean local) {
|
|
48
|
+
SendBirdVideoView videoView = view.getSurfaceViewRenderer();
|
|
49
|
+
if(directCall == null) return;
|
|
50
|
+
if(local) {
|
|
51
|
+
directCall.setLocalVideoView(videoView);
|
|
52
|
+
}else {
|
|
53
|
+
directCall.setRemoteVideoView(videoView);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@ReactProp(name = "scaleFit", defaultBoolean = true)
|
|
58
|
+
public void scaleFit(RNVSendBirdCallsVideo view, @Nullable Boolean scaleFit) {
|
|
59
|
+
SendBirdVideoView videoView = view.getSurfaceViewRenderer();
|
|
60
|
+
if(directCall == null) return;
|
|
61
|
+
if(scaleFit) {
|
|
62
|
+
view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
|
|
63
|
+
}else {
|
|
64
|
+
view.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public void manuallyLayoutChildren(View view) {
|
|
69
|
+
// propWidth and propHeight coming from react-native props
|
|
70
|
+
int width = propWidth;
|
|
71
|
+
int height = propHeight;
|
|
72
|
+
view.measure(
|
|
73
|
+
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
|
|
74
|
+
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
|
|
75
|
+
view.layout(0, 0, width, height);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Point;
|
|
4
|
+
import android.view.ViewGroup;
|
|
5
|
+
|
|
6
|
+
import androidx.annotation.StringDef;
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
9
|
+
import com.sendbird.calls.SendBirdVideoView;
|
|
10
|
+
|
|
11
|
+
import org.webrtc.RendererCommon;
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import java.lang.annotation.Retention;
|
|
15
|
+
import java.lang.annotation.RetentionPolicy;
|
|
16
|
+
|
|
17
|
+
import static com.rnsendbirdcalls.RNVideoViewGroup.Events.ON_FRAME_DIMENSIONS_CHANGED;
|
|
18
|
+
|
|
19
|
+
public class RNVideoViewGroup extends ViewGroup {
|
|
20
|
+
private SendBirdVideoView surfaceViewRenderer = null;
|
|
21
|
+
private int videoWidth = 0;
|
|
22
|
+
private int videoHeight = 0;
|
|
23
|
+
private final Object layoutSync = new Object();
|
|
24
|
+
private RendererCommon.ScalingType scalingType = RendererCommon.ScalingType.SCALE_ASPECT_FIT;
|
|
25
|
+
|
|
26
|
+
@Retention(RetentionPolicy.SOURCE)
|
|
27
|
+
@StringDef({ON_FRAME_DIMENSIONS_CHANGED})
|
|
28
|
+
public @interface Events {
|
|
29
|
+
String ON_FRAME_DIMENSIONS_CHANGED = "onFrameDimensionsChanged";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public RNVideoViewGroup(ThemedReactContext themedReactContext) {
|
|
33
|
+
super(themedReactContext);
|
|
34
|
+
surfaceViewRenderer = new SendBirdVideoView(themedReactContext);
|
|
35
|
+
surfaceViewRenderer.setEnableHardwareScaler(true);
|
|
36
|
+
addView(surfaceViewRenderer);
|
|
37
|
+
|
|
38
|
+
// surfaceViewRenderer.setListener(
|
|
39
|
+
// new RendererCommon.RendererEvents() {
|
|
40
|
+
// @Override
|
|
41
|
+
// public void onFirstFrameRendered() {
|
|
42
|
+
//
|
|
43
|
+
// }
|
|
44
|
+
//
|
|
45
|
+
// @Override
|
|
46
|
+
// public void onFrameResolutionChanged(int vw, int vh, int rotation) {
|
|
47
|
+
// synchronized (layoutSync) {
|
|
48
|
+
// if (rotation == 90 || rotation == 270) {
|
|
49
|
+
// videoHeight = vw;
|
|
50
|
+
// videoWidth = vh;
|
|
51
|
+
// } else {
|
|
52
|
+
// videoHeight = vh;
|
|
53
|
+
// videoWidth = vw;
|
|
54
|
+
// }
|
|
55
|
+
// RNVideoViewGroup.this.forceLayout();
|
|
56
|
+
//
|
|
57
|
+
// WritableMap event = new WritableNativeMap();
|
|
58
|
+
// event.putInt("height", vh);
|
|
59
|
+
// event.putInt("width", vw);
|
|
60
|
+
// event.putInt("rotation", rotation);
|
|
61
|
+
// pushEvent(RNVideoViewGroup.this, ON_FRAME_DIMENSIONS_CHANGED, event);
|
|
62
|
+
// }
|
|
63
|
+
// }
|
|
64
|
+
// }
|
|
65
|
+
// );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public SendBirdVideoView getSurfaceViewRenderer() {
|
|
69
|
+
return surfaceViewRenderer;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public void setScalingType(RendererCommon.ScalingType scalingType) {
|
|
73
|
+
this.scalingType = scalingType;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Override
|
|
77
|
+
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
78
|
+
int height = b - t;
|
|
79
|
+
int width = r - l;
|
|
80
|
+
if (height == 0 || width == 0) {
|
|
81
|
+
l = t = r = b = 0;
|
|
82
|
+
} else {
|
|
83
|
+
int videoHeight;
|
|
84
|
+
int videoWidth;
|
|
85
|
+
synchronized (layoutSync) {
|
|
86
|
+
videoHeight = this.videoHeight;
|
|
87
|
+
videoWidth = this.videoWidth;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (videoHeight == 0 || videoWidth == 0) {
|
|
91
|
+
videoHeight = 480;
|
|
92
|
+
videoWidth = 640;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Point displaySize = RendererCommon.getDisplaySize(
|
|
96
|
+
this.scalingType,
|
|
97
|
+
videoWidth / (float) videoHeight,
|
|
98
|
+
width,
|
|
99
|
+
height
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
l = (width - displaySize.x) / 2;
|
|
103
|
+
t = (height - displaySize.y) / 2;
|
|
104
|
+
r = l + displaySize.x;
|
|
105
|
+
b = t + displaySize.y;
|
|
106
|
+
}
|
|
107
|
+
surfaceViewRenderer.layout(l, t, r, b);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint;
|
|
4
|
+
import android.app.ActivityManager;
|
|
5
|
+
import android.app.KeyguardManager;
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.content.Intent;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.os.Bundle;
|
|
10
|
+
import android.os.PowerManager;
|
|
11
|
+
import android.util.Log;
|
|
12
|
+
|
|
13
|
+
import com.facebook.react.bridge.ReactContext;
|
|
14
|
+
import com.facebook.react.common.LifecycleState;
|
|
15
|
+
|
|
16
|
+
import java.util.List;
|
|
17
|
+
|
|
18
|
+
@SuppressWarnings({"unused", "JavaDoc", "WeakerAccess"})
|
|
19
|
+
public class SharedUtils {
|
|
20
|
+
/**
|
|
21
|
+
* We need to check if app is in foreground otherwise the app will crash.
|
|
22
|
+
* http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
|
|
23
|
+
*
|
|
24
|
+
* @param context Context
|
|
25
|
+
* @return boolean
|
|
26
|
+
*/
|
|
27
|
+
public static boolean isAppInForeground(Context context) {
|
|
28
|
+
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
29
|
+
if (activityManager == null) return false;
|
|
30
|
+
|
|
31
|
+
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
|
|
32
|
+
if (appProcesses == null) return false;
|
|
33
|
+
|
|
34
|
+
final String packageName = context.getPackageName();
|
|
35
|
+
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
|
|
36
|
+
if (
|
|
37
|
+
appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
|
|
38
|
+
&& appProcess.processName.equals(packageName)
|
|
39
|
+
) {
|
|
40
|
+
ReactContext reactContext;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
reactContext = (ReactContext) context;
|
|
44
|
+
} catch (ClassCastException exception) {
|
|
45
|
+
// Not react context so default to true
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return reactContext.getLifecycleState() == LifecycleState.RESUMED;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public static boolean getDeviceLocked(Context context) {
|
|
57
|
+
boolean isLocked = false;
|
|
58
|
+
// First we check the locked state
|
|
59
|
+
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(context.KEYGUARD_SERVICE);
|
|
60
|
+
boolean inKeyguardRestrictedInputMode = keyguardManager.inKeyguardRestrictedInputMode();
|
|
61
|
+
if (inKeyguardRestrictedInputMode) {
|
|
62
|
+
isLocked = true;
|
|
63
|
+
} else {
|
|
64
|
+
// If password is not set in the settings, the inKeyguardRestrictedInputMode() returns false,
|
|
65
|
+
// so we need to check if screen on for this case
|
|
66
|
+
PowerManager powerManager = (PowerManager) context.getSystemService(context.POWER_SERVICE);
|
|
67
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
|
|
68
|
+
isLocked = !powerManager.isInteractive();
|
|
69
|
+
} else {
|
|
70
|
+
//noinspection deprecation
|
|
71
|
+
isLocked = !powerManager.isScreenOn();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return isLocked;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public static Class<?> getMainActivityClass(Context appContext){
|
|
78
|
+
try {
|
|
79
|
+
final Package appPackage = appContext.getClass().getPackage();
|
|
80
|
+
assert appPackage != null;
|
|
81
|
+
final String className = appPackage.getName() + ".MainActivity";
|
|
82
|
+
return Class.forName(className);
|
|
83
|
+
} catch (Exception e){
|
|
84
|
+
e.printStackTrace();
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public static void navigateMainApp(Context context, Bundle bundle) {
|
|
90
|
+
try {
|
|
91
|
+
Intent intent = new Intent(context, getMainActivityClass(context));
|
|
92
|
+
intent.putExtras(bundle);
|
|
93
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
94
|
+
context.startActivity(intent);
|
|
95
|
+
cancelComingCallNotification();
|
|
96
|
+
}catch (Exception e){
|
|
97
|
+
e.printStackTrace();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public static void startComingCallActivity(Context context, Bundle bundle) {
|
|
102
|
+
try {
|
|
103
|
+
Intent intent = new Intent(context, ComingCallActivity.class);
|
|
104
|
+
intent.putExtras(bundle);
|
|
105
|
+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
106
|
+
context.startActivity(intent);
|
|
107
|
+
}catch (Exception e){
|
|
108
|
+
e.printStackTrace();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public static void cancelComingCallNotification(){
|
|
113
|
+
VoIPBaseService instance = VoIPService.getSharedInstance();
|
|
114
|
+
if(instance != null) {
|
|
115
|
+
instance.stopSelf();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@SuppressLint("PrivateApi")
|
|
120
|
+
public static String getSystemProperty(String key) {
|
|
121
|
+
try {
|
|
122
|
+
Class props = Class.forName("android.os.SystemProperties");
|
|
123
|
+
return (String) props.getMethod("get", String.class).invoke(null, key);
|
|
124
|
+
} catch (Exception ignore) { }
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import android.content.BroadcastReceiver;
|
|
5
|
+
import android.content.Context;
|
|
6
|
+
import android.content.Intent;
|
|
7
|
+
import android.os.Bundle;
|
|
8
|
+
|
|
9
|
+
import com.facebook.react.HeadlessJsTaskService;
|
|
10
|
+
|
|
11
|
+
public class VcCallReceiver extends BroadcastReceiver {
|
|
12
|
+
|
|
13
|
+
private void acceptIncomingCallFromNotification(Context context, Bundle extras) {
|
|
14
|
+
extras.putString("CALL_STATUS", "ANSWERED");
|
|
15
|
+
SharedUtils.navigateMainApp(context, extras);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@Override
|
|
19
|
+
public void onReceive(Context context, Intent intent) {
|
|
20
|
+
Bundle extras = intent.getExtras();
|
|
21
|
+
if ((context.getPackageName() + ".DECLINE_CALL").equals(intent.getAction())) {
|
|
22
|
+
if(extras != null) extras.putString("ACTION", "DENIED");
|
|
23
|
+
} else if ((context.getPackageName() + ".ANSWER_CALL").equals(intent.getAction())) {
|
|
24
|
+
if(extras != null) extras.putString("ACTION", "ACCEPT");
|
|
25
|
+
acceptIncomingCallFromNotification(context.getApplicationContext(), extras);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Intent serviceIntent = new Intent(context, VcHeadlessService.class);
|
|
29
|
+
serviceIntent.putExtras(extras);
|
|
30
|
+
context.startService(serviceIntent);
|
|
31
|
+
HeadlessJsTaskService.acquireWakeLockNow(context);
|
|
32
|
+
|
|
33
|
+
if (VoIPBaseService.getSharedInstance() != null) {
|
|
34
|
+
VoIPBaseService.getSharedInstance().stopSelf();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.content.Intent;
|
|
4
|
+
import android.os.Bundle;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.HeadlessJsTaskService;
|
|
7
|
+
import com.facebook.react.bridge.Arguments;
|
|
8
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
|
|
9
|
+
import javax.annotation.Nullable;
|
|
10
|
+
|
|
11
|
+
public class VcHeadlessService extends HeadlessJsTaskService {
|
|
12
|
+
|
|
13
|
+
@Override
|
|
14
|
+
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
|
|
15
|
+
Bundle extras = intent.getExtras();
|
|
16
|
+
return new HeadlessJsTaskConfig(
|
|
17
|
+
"VcHeadlessService",
|
|
18
|
+
Arguments.fromBundle(extras),
|
|
19
|
+
60000, // timeout for the task
|
|
20
|
+
true // optional: defines whether or not the task is allowed in foreground. Default is false
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint;
|
|
4
|
+
import android.app.KeyguardManager;
|
|
5
|
+
import android.app.NotificationManager;
|
|
6
|
+
import android.app.Service;
|
|
7
|
+
import android.content.Context;
|
|
8
|
+
import android.content.Intent;
|
|
9
|
+
import android.content.IntentFilter;
|
|
10
|
+
import android.net.ConnectivityManager;
|
|
11
|
+
import android.os.Build;
|
|
12
|
+
import android.os.PowerManager;
|
|
13
|
+
import android.telephony.TelephonyManager;
|
|
14
|
+
import android.util.Log;
|
|
15
|
+
|
|
16
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
17
|
+
|
|
18
|
+
@SuppressLint("NewApi")
|
|
19
|
+
public abstract class VoIPBaseService extends Service {
|
|
20
|
+
|
|
21
|
+
private String TAG = "VOIP_BASE_SERVICE_TAG";
|
|
22
|
+
|
|
23
|
+
protected static final int ID_INCOMING_CALL_NOTIFICATION = 202;
|
|
24
|
+
|
|
25
|
+
protected static final String NOTIFICATION_CHANNEL = "edoctor_call_channel";
|
|
26
|
+
|
|
27
|
+
protected static final boolean USE_CONNECTION_SERVICE = isDeviceCompatibleWithConnectionServiceAPI();
|
|
28
|
+
|
|
29
|
+
protected static VoIPBaseService sharedInstance;
|
|
30
|
+
protected boolean notificationsDisabled = false;
|
|
31
|
+
protected PowerManager.WakeLock proximityWakelock;
|
|
32
|
+
protected PowerManager.WakeLock cpuWakelock;
|
|
33
|
+
|
|
34
|
+
public static VoIPBaseService getSharedInstance() { return sharedInstance; }
|
|
35
|
+
|
|
36
|
+
@Override
|
|
37
|
+
public void onDestroy() {
|
|
38
|
+
Log.d(TAG, "======== VoIP SERVICE STOPPING ========");
|
|
39
|
+
stopForeground(true);
|
|
40
|
+
if (proximityWakelock != null && proximityWakelock.isHeld()) {
|
|
41
|
+
proximityWakelock.release();
|
|
42
|
+
}
|
|
43
|
+
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
44
|
+
manager.cancel(ID_INCOMING_CALL_NOTIFICATION);
|
|
45
|
+
super.onDestroy();
|
|
46
|
+
if (cpuWakelock != null && cpuWakelock.isHeld()) {
|
|
47
|
+
cpuWakelock.release();
|
|
48
|
+
}
|
|
49
|
+
sharedInstance = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@SuppressLint("InvalidWakeLockTag")
|
|
53
|
+
@Override
|
|
54
|
+
public void onCreate() {
|
|
55
|
+
super.onCreate();
|
|
56
|
+
Log.d(TAG, "======== VoIP SERVICE STARTING ========");
|
|
57
|
+
try {
|
|
58
|
+
cpuWakelock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "edoctor-voip");
|
|
59
|
+
cpuWakelock.acquire();
|
|
60
|
+
IntentFilter filter = new IntentFilter();
|
|
61
|
+
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
|
62
|
+
if (!USE_CONNECTION_SERVICE) {
|
|
63
|
+
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
|
|
64
|
+
filter.addAction(Intent.ACTION_SCREEN_ON);
|
|
65
|
+
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
|
66
|
+
}
|
|
67
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled()) {
|
|
68
|
+
notificationsDisabled = true;
|
|
69
|
+
}
|
|
70
|
+
} catch (Exception x) { }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected boolean isAcknowledgeCall(){
|
|
74
|
+
boolean isAcknowledge = true;
|
|
75
|
+
if (Build.VERSION.SDK_INT >= 19 && XiaomiUtilities.isMIUI() && !XiaomiUtilities.isCustomPermissionGranted(this.getApplicationContext(), XiaomiUtilities.OP_SHOW_WHEN_LOCKED)) {
|
|
76
|
+
if (((KeyguardManager) getSystemService(KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode()) {
|
|
77
|
+
Log.d(TAG, "MIUI: no permission to show when locked but the screen is locked. ¯\\_(ツ)_/¯");
|
|
78
|
+
isAcknowledge = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return isAcknowledge;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private static boolean isDeviceCompatibleWithConnectionServiceAPI() {
|
|
85
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
// some non-Google devices don't implement the ConnectionService API correctly so, sadly,
|
|
89
|
+
// we'll have to whitelist only a handful of known-compatible devices for now
|
|
90
|
+
return false;/*"angler".equals(Build.PRODUCT) // Nexus 6P
|
|
91
|
+
|| "bullhead".equals(Build.PRODUCT) // Nexus 5X
|
|
92
|
+
|| "sailfish".equals(Build.PRODUCT) // Pixel
|
|
93
|
+
|| "marlin".equals(Build.PRODUCT) // Pixel XL
|
|
94
|
+
|| "walleye".equals(Build.PRODUCT) // Pixel 2
|
|
95
|
+
|| "taimen".equals(Build.PRODUCT) // Pixel 2 XL
|
|
96
|
+
|| "blueline".equals(Build.PRODUCT) // Pixel 3
|
|
97
|
+
|| "crosshatch".equals(Build.PRODUCT) // Pixel 3 XL
|
|
98
|
+
|| MessagesController.getGlobalMainSettings().getBoolean("dbg_force_connection_service", false);*/
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
package com.rnsendbirdcalls;
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint;
|
|
4
|
+
import android.app.Notification;
|
|
5
|
+
import android.app.NotificationChannel;
|
|
6
|
+
import android.app.NotificationManager;
|
|
7
|
+
import android.app.PendingIntent;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.Intent;
|
|
10
|
+
import android.os.Build;
|
|
11
|
+
import android.os.Bundle;
|
|
12
|
+
import android.os.IBinder;
|
|
13
|
+
import android.text.SpannableString;
|
|
14
|
+
import android.text.style.ForegroundColorSpan;
|
|
15
|
+
import android.util.Log;
|
|
16
|
+
import android.widget.RemoteViews;
|
|
17
|
+
|
|
18
|
+
import androidx.annotation.Nullable;
|
|
19
|
+
|
|
20
|
+
@SuppressLint("NewApi")
|
|
21
|
+
public class VoIPService extends VoIPBaseService {
|
|
22
|
+
|
|
23
|
+
private String TAG = "VOIP_SERVICE_TAG";
|
|
24
|
+
|
|
25
|
+
@Override
|
|
26
|
+
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
27
|
+
sharedInstance = this;
|
|
28
|
+
if(!isAcknowledgeCall()) { stopSelf(); return START_NOT_STICKY; }
|
|
29
|
+
Bundle bundle = intent.getExtras();
|
|
30
|
+
if (!notificationsDisabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
31
|
+
showIncomingNotification(bundle);
|
|
32
|
+
} else {
|
|
33
|
+
SharedUtils.navigateMainApp(this, bundle);
|
|
34
|
+
}
|
|
35
|
+
return START_NOT_STICKY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected void showIncomingNotification(Bundle bundle) {
|
|
39
|
+
String notificationTitle = bundle.getString("title");
|
|
40
|
+
if(notificationTitle == null) notificationTitle = "Cuộc gọi đến";
|
|
41
|
+
else bundle.remove("title");
|
|
42
|
+
|
|
43
|
+
String notificationDescription = bundle.getString("description");
|
|
44
|
+
if(notificationDescription == null) notificationDescription = "...";
|
|
45
|
+
else bundle.remove("description");
|
|
46
|
+
|
|
47
|
+
long timeoutAfter = (long)bundle.getDouble("timeoutAfter");
|
|
48
|
+
if(timeoutAfter == 0) timeoutAfter = 60000;
|
|
49
|
+
else bundle.remove("timeoutAfter");
|
|
50
|
+
|
|
51
|
+
Intent intentComingCall;
|
|
52
|
+
Boolean isDeviceLocked = SharedUtils.getDeviceLocked(this.getApplicationContext());
|
|
53
|
+
Log.d(TAG, "isDeviceLocked: isDeviceLocked" + isDeviceLocked);
|
|
54
|
+
if (isDeviceLocked) {
|
|
55
|
+
intentComingCall = new Intent(this, ComingCallActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
56
|
+
.putExtras(bundle);
|
|
57
|
+
} else {
|
|
58
|
+
intentComingCall = new Intent(this, SharedUtils.getMainActivityClass(this.getApplicationContext())).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
59
|
+
.putExtras(bundle);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
Notification.Builder builder = new Notification.Builder(this)
|
|
64
|
+
.setContentTitle(notificationTitle)
|
|
65
|
+
.setContentText(notificationDescription)
|
|
66
|
+
.setSmallIcon(android.R.drawable.sym_action_call)
|
|
67
|
+
.setAutoCancel(true)
|
|
68
|
+
.setPriority(Notification.PRIORITY_MAX)
|
|
69
|
+
.setOngoing(true)
|
|
70
|
+
.setShowWhen(true)
|
|
71
|
+
.setContentIntent(
|
|
72
|
+
PendingIntent.getActivity(
|
|
73
|
+
this,
|
|
74
|
+
0,
|
|
75
|
+
intentComingCall,
|
|
76
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
|
|
81
|
+
builder.setTimeoutAfter(timeoutAfter);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// create channel android sdk >= 26
|
|
85
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
86
|
+
NotificationManager NMmanager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
87
|
+
if (NMmanager.getNotificationChannel(NOTIFICATION_CHANNEL) == null) {
|
|
88
|
+
NMmanager.createNotificationChannel(
|
|
89
|
+
new NotificationChannel(NOTIFICATION_CHANNEL, "Nhận cuộc gọi", NotificationManager.IMPORTANCE_HIGH)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
builder.setChannelId(NOTIFICATION_CHANNEL);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// create end PendingIntent
|
|
96
|
+
Intent endIntent = new Intent(this, VcCallReceiver.class).putExtras(bundle);
|
|
97
|
+
endIntent.setAction(getPackageName() + ".DECLINE_CALL");
|
|
98
|
+
CharSequence endTitle = "Từ chối";
|
|
99
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
100
|
+
endTitle = new SpannableString(endTitle);
|
|
101
|
+
((SpannableString) endTitle).setSpan(new ForegroundColorSpan(0xFFF44336), 0, endTitle.length(), 0);
|
|
102
|
+
}
|
|
103
|
+
PendingIntent endPendingIntent = PendingIntent.getBroadcast(
|
|
104
|
+
this,
|
|
105
|
+
0,
|
|
106
|
+
endIntent,
|
|
107
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
|
108
|
+
);
|
|
109
|
+
builder.addAction(R.drawable.button_reject, endTitle, endPendingIntent);
|
|
110
|
+
// create end PendingIntent
|
|
111
|
+
|
|
112
|
+
// create answer PendingIntent
|
|
113
|
+
Intent answerIntent = new Intent(this, SharedUtils.getMainActivityClass(this.getApplicationContext())).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
114
|
+
.putExtras(bundle);
|
|
115
|
+
answerIntent.setAction(getPackageName() + ".ANSWER_CALL");
|
|
116
|
+
CharSequence answerTitle = "Đồng ý";
|
|
117
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
118
|
+
answerTitle = new SpannableString(answerTitle);
|
|
119
|
+
((SpannableString) answerTitle).setSpan(new ForegroundColorSpan(0xFF00AA00), 0, answerTitle.length(), 0);
|
|
120
|
+
}
|
|
121
|
+
PendingIntent answerPendingIntent = PendingIntent.getActivity(
|
|
122
|
+
this,
|
|
123
|
+
0,
|
|
124
|
+
answerIntent,
|
|
125
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
builder.addAction(R.drawable.button_answer, answerTitle, answerPendingIntent);
|
|
129
|
+
// create answer PendingIntent
|
|
130
|
+
|
|
131
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
132
|
+
builder.setShowWhen(false);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Notification incomingNotification = builder.getNotification();
|
|
136
|
+
|
|
137
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
138
|
+
builder.setCategory(Notification.CATEGORY_CALL);
|
|
139
|
+
PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(
|
|
140
|
+
this,
|
|
141
|
+
0,
|
|
142
|
+
intentComingCall,
|
|
143
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
|
144
|
+
);
|
|
145
|
+
builder.setFullScreenIntent(fullScreenPendingIntent, true);
|
|
146
|
+
RemoteViews customView = new RemoteViews(getPackageName(), R.layout.call_notification);
|
|
147
|
+
customView.setTextViewText(R.id.title, notificationTitle);
|
|
148
|
+
customView.setTextViewText(R.id.subtitle, notificationDescription);
|
|
149
|
+
customView.setTextViewText(R.id.answer_text, "Đồng ý");
|
|
150
|
+
customView.setTextViewText(R.id.decline_text, "Từ chối");
|
|
151
|
+
customView.setOnClickPendingIntent(R.id.container, fullScreenPendingIntent);
|
|
152
|
+
customView.setOnClickPendingIntent(R.id.answer_btn, answerPendingIntent);
|
|
153
|
+
customView.setOnClickPendingIntent(R.id.decline_btn, endPendingIntent);
|
|
154
|
+
incomingNotification.headsUpContentView = incomingNotification.bigContentView = customView;
|
|
155
|
+
}
|
|
156
|
+
startForeground(ID_INCOMING_CALL_NOTIFICATION, incomingNotification);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Nullable
|
|
160
|
+
@Override
|
|
161
|
+
public IBinder onBind(Intent intent) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|