Skip to content

Commit f6f11ad

Browse files
authored
fix: warn natively if notifee is not configured for keep call alive (#1678)
fixes #1587 Now even if notifee is installed, but not configured properly, we skip call alive part from SDK.. upon inspecting native logs they can see whats not configured properly, its on warn level for easiness
1 parent 7d913fd commit f6f11ad

File tree

4 files changed

+203
-7
lines changed

4 files changed

+203
-7
lines changed

packages/react-native-sdk/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativeModule.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import com.facebook.react.bridge.ReactApplicationContext
1212
import com.facebook.react.bridge.ReactContextBaseJavaModule
1313
import com.facebook.react.bridge.ReactMethod
1414
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
15+
import com.streamvideo.reactnative.util.CallAlivePermissionsHelper
16+
import com.streamvideo.reactnative.util.CallAliveServiceChecker
1517
import com.streamvideo.reactnative.util.PiPHelper
1618
import com.streamvideo.reactnative.util.RingtoneUtil
1719

@@ -59,6 +61,22 @@ class StreamVideoReactNativeModule(reactContext: ReactApplicationContext) :
5961
}
6062
}
6163

64+
@ReactMethod
65+
fun isCallAliveConfigured(promise: Promise) {
66+
val permissionsDeclared =
67+
CallAlivePermissionsHelper.hasForegroundServicePermissionsDeclared(reactApplicationContext)
68+
if (!permissionsDeclared) {
69+
promise.resolve(false)
70+
return
71+
}
72+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
73+
val isForegroundServiceDeclared = CallAliveServiceChecker.isForegroundServiceDeclared(reactApplicationContext)
74+
promise.resolve(isForegroundServiceDeclared)
75+
} else {
76+
promise.resolve(true)
77+
}
78+
}
79+
6280
@Suppress("UNUSED_PARAMETER")
6381
@ReactMethod
6482
fun addListener(eventName: String?) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.streamvideo.reactnative.util
2+
3+
import android.content.pm.PackageInfo
4+
import android.content.pm.PackageManager
5+
import android.util.Log
6+
import com.facebook.react.bridge.ReactApplicationContext
7+
8+
9+
object CallAlivePermissionsHelper {
10+
private const val NAME = "StreamVideoReactNative"
11+
12+
fun hasForegroundServicePermissionsDeclared(context: ReactApplicationContext): Boolean {
13+
val packageManager = context.packageManager
14+
val packageName = context.packageName
15+
16+
try {
17+
val packageInfo: PackageInfo =
18+
packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
19+
val requestedPermissions = packageInfo.requestedPermissions
20+
21+
if (requestedPermissions != null) {
22+
val requiredPermissions = arrayOf(
23+
"android.permission.FOREGROUND_SERVICE",
24+
"android.permission.FOREGROUND_SERVICE_CAMERA",
25+
"android.permission.FOREGROUND_SERVICE_MICROPHONE",
26+
"android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE",
27+
"android.permission.FOREGROUND_SERVICE_DATA_SYNC"
28+
)
29+
30+
val missingPermissions =
31+
requiredPermissions.filterNot { requestedPermissions.contains(it) }
32+
33+
if (missingPermissions.isNotEmpty()) {
34+
Log.w(
35+
NAME,
36+
"Missing ForegroundServicePermissions: ${missingPermissions.joinToString(", ")}"
37+
)
38+
return false
39+
} else {
40+
return true
41+
}
42+
}
43+
} catch (e: PackageManager.NameNotFoundException) {
44+
// do nothing, this can never happen actually
45+
Log.e(
46+
NAME,
47+
"Package not found: $packageName",
48+
e
49+
)
50+
}
51+
return false
52+
}
53+
54+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.streamvideo.reactnative.util
2+
3+
import android.content.ComponentName
4+
import android.content.pm.PackageManager
5+
import android.content.pm.ServiceInfo
6+
import android.os.Build
7+
import android.util.Log
8+
import androidx.annotation.RequiresApi
9+
import com.facebook.react.bridge.ReactApplicationContext
10+
11+
@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
12+
object CallAliveServiceChecker {
13+
private const val NAME = "StreamVideoReactNative"
14+
15+
fun isForegroundServiceDeclared(context: ReactApplicationContext): Boolean {
16+
val packageManager = context.packageManager
17+
val packageName = context.packageName // Get the package name of your app
18+
val componentName = ComponentName(
19+
packageName,
20+
"app.notifee.core.ForegroundService"
21+
) // Use service name string
22+
23+
try {
24+
val serviceInfo =
25+
packageManager.getServiceInfo(componentName, PackageManager.GET_META_DATA)
26+
27+
val expectedForegroundServiceTypes =
28+
ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE or
29+
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC or
30+
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or
31+
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or
32+
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
33+
34+
val actualForegroundServiceType = serviceInfo.foregroundServiceType
35+
36+
if (actualForegroundServiceType == expectedForegroundServiceTypes) {
37+
return true
38+
} else {
39+
Log.w(
40+
NAME,
41+
"android:foregroundServiceType does not match: expected=${
42+
foregroundServiceTypeToString(
43+
expectedForegroundServiceTypes
44+
)
45+
}, actual=${foregroundServiceTypeToString(actualForegroundServiceType)}"
46+
)
47+
return false
48+
}
49+
50+
} catch (e: PackageManager.NameNotFoundException) {
51+
Log.d(NAME, "Service not found: " + e.message)
52+
return false // Service not declared
53+
}
54+
}
55+
56+
private fun foregroundServiceTypeToString(foregroundServiceType: Int): String {
57+
val types = mutableListOf<String>()
58+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE != 0) {
59+
types.add("shortService")
60+
}
61+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC != 0) {
62+
types.add("dataSync")
63+
}
64+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA != 0) {
65+
types.add("camera")
66+
}
67+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE != 0) {
68+
types.add("microphone")
69+
}
70+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE != 0) {
71+
types.add("connectedDevice")
72+
}
73+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION != 0) {
74+
types.add("location")
75+
}
76+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK != 0) {
77+
types.add("mediaPlayback")
78+
}
79+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION != 0) {
80+
types.add("mediaProjection")
81+
}
82+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL != 0) {
83+
types.add("phoneCall")
84+
}
85+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH != 0) {
86+
types.add("health")
87+
}
88+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING != 0) {
89+
types.add("remoteMessaging")
90+
}
91+
if (foregroundServiceType and ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED != 0) {
92+
types.add("systemExempted")
93+
}
94+
return types.joinToString("|")
95+
}
96+
}

packages/react-native-sdk/src/hooks/useAndroidKeepCallAliveEffect.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
22
import { useEffect, useRef } from 'react';
33
import { StreamVideoRN } from '../utils';
4-
import { AppState, AppStateStatus, Platform } from 'react-native';
4+
import {
5+
NativeModules,
6+
AppState,
7+
AppStateStatus,
8+
Platform,
9+
} from 'react-native';
510
import { CallingState, getLogger } from '@stream-io/video-client';
611
import {
712
getNotifeeLibNoThrowForKeepCallAlive,
@@ -11,12 +16,25 @@ import {
1116
const notifeeLib = getNotifeeLibNoThrowForKeepCallAlive();
1217

1318
function setForegroundService() {
14-
notifeeLib?.default.registerForegroundService(() => {
15-
return new Promise(() => {
16-
const logger = getLogger(['setForegroundService method']);
17-
logger('info', 'Foreground service running for call in progress');
18-
});
19-
});
19+
if (Platform.OS === 'ios' || !notifeeLib) return;
20+
NativeModules.StreamVideoReactNative.isCallAliveConfigured().then(
21+
(isConfigured: boolean) => {
22+
if (!isConfigured) {
23+
const logger = getLogger(['setForegroundService method']);
24+
logger(
25+
'info',
26+
'KeepCallAlive is not configured. Skipping foreground service setup.'
27+
);
28+
return;
29+
}
30+
notifeeLib.default.registerForegroundService(() => {
31+
return new Promise(() => {
32+
const logger = getLogger(['setForegroundService method']);
33+
logger('info', 'Foreground service running for call in progress');
34+
});
35+
});
36+
}
37+
);
2038
}
2139

2240
async function startForegroundService(call_cid: string) {
@@ -25,6 +43,16 @@ async function startForegroundService(call_cid: string) {
2543

2644
// check for notification permission and then start the foreground service
2745
if (!notifeeLib) return;
46+
const isCallAliveConfigured =
47+
await NativeModules.StreamVideoReactNative.isCallAliveConfigured();
48+
if (!isCallAliveConfigured) {
49+
const logger = getLogger(['startForegroundService']);
50+
logger(
51+
'info',
52+
'KeepCallAlive is not configured. Skipping foreground service setup.'
53+
);
54+
return;
55+
}
2856
const settings = await notifeeLib.default.getNotificationSettings();
2957
if (
3058
settings.authorizationStatus !== notifeeLib.AuthorizationStatus.AUTHORIZED

0 commit comments

Comments
 (0)