Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cursor/skills/agent-skills
Submodule agent-skills added at a4f602
5 changes: 0 additions & 5 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,6 @@
android:enabled="true"
android:exported="false" />

<service
android:name="chat.rocket.reactnative.voip.VoipForegroundService"
android:exported="false"
android:foregroundServiceType="phoneCall" />

<service android:name="io.wazo.callkeep.VoiceConnectionService"
android:label="Wazo"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
RNBootSplash.init(this, R.style.BootTheme)
super.onCreate(null)

// Handle notification intents
intent?.let { NotificationIntentHandler.handleIntent(this, it) }
}

public override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)

// Handle notification intents when activity is already running
NotificationIntentHandler.handleIntent(this, intent)
}

override fun invokeDefaultOnBackPressed() {
moveTaskToBack(true)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import android.os.Bundle
import android.util.Log
import com.google.gson.GsonBuilder
import chat.rocket.reactnative.voip.VoipNotification
import chat.rocket.reactnative.voip.VoipModule
import chat.rocket.reactnative.voip.VoipPayload
import android.os.Build
import android.app.KeyguardManager
import android.app.Activity

/**
* Handles notification Intent processing from MainActivity.
Expand All @@ -27,8 +22,7 @@ class NotificationIntentHandler {
*/
@JvmStatic
fun handleIntent(context: Context, intent: Intent) {
// Handle VoIP action first
if (handleVoipIntent(context, intent)) {
if (VoipNotification.handleMainActivityVoipIntent(context, intent)) {
return
}

Expand All @@ -41,41 +35,6 @@ class NotificationIntentHandler {
handleNotificationIntent(context, intent)
}

/**
* Handles VoIP call notification Intent.
* @return true if this was a VoIP intent, false otherwise
*/
@JvmStatic
private fun handleVoipIntent(context: Context, intent: Intent): Boolean {
if (!intent.getBooleanExtra("voipAction", false)) {
return false
}
val voipPayload = VoipPayload.fromBundle(intent.extras)
if (voipPayload == null || !voipPayload.isVoipIncomingCall()) {
return false
}

Log.d(TAG, "Handling VoIP intent - voipPayload: $voipPayload")

VoipNotification.cancelById(context, voipPayload.notificationId)
VoipNotification.cancelTimeout(voipPayload.callId)
VoipModule.storeInitialEvents(voipPayload)

if (context is Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
context.setShowWhenLocked(true)
context.setTurnScreenOn(true)
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
keyguardManager.requestDismissKeyguard(context, null)
}
}

// Clear the voip flag to prevent re-processing
intent.removeExtra("voipAction")

return true
}

/**
* Handles video conference notification Intent.
* @return true if this was a video conf intent, false otherwise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.os.Bundle
import android.util.Log
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.google.gson.Gson
import chat.rocket.reactnative.voip.VoipNotification
import chat.rocket.reactnative.voip.VoipPayload

Expand All @@ -19,7 +18,6 @@ class RCFirebaseMessagingService : FirebaseMessagingService() {

companion object {
private const val TAG = "RocketChat.FCM"
private val gson = Gson()
}

override fun onMessageReceived(remoteMessage: RemoteMessage) {
Expand Down Expand Up @@ -53,22 +51,6 @@ class RCFirebaseMessagingService : FirebaseMessagingService() {
}
}

/**
* Safely parses ejson string to Ejson object.
*/
private fun parseEjson(ejsonStr: String?): Ejson? {
if (ejsonStr.isNullOrEmpty() || ejsonStr == "{}") {
return null
}

return try {
gson.fromJson(ejsonStr, Ejson::class.java)
} catch (e: Exception) {
Log.e(TAG, "Failed to parse ejson", e)
null
}
}

override fun onNewToken(token: String) {
Log.d(TAG, "FCM token refreshed")
// Token handling is done by expo-notifications JS layer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import android.widget.FrameLayout
import android.util.Log
import android.view.ViewOutlineProvider
import com.bumptech.glide.Glide
import chat.rocket.reactnative.MainActivity
import chat.rocket.reactnative.R
import android.graphics.Typeface
import chat.rocket.reactnative.notification.Ejson
Expand Down Expand Up @@ -283,15 +282,8 @@ class IncomingCallActivity : Activity() {
clearTimeout()
VoipNotification.cancelTimeout(payload.callId)
stopRingtone()

// Launch MainActivity with call data
val launchIntent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtras(payload.toBundle())
}
startActivity(launchIntent)

finish()
VoipNotification.handleAcceptAction(this, payload)
// Activity finishes when ACTION_DISMISS is broadcast from handleAcceptAction (async DDP).
}

private fun handleDecline(payload: VoipPayload) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class VoipModule(reactContext: ReactApplicationContext) : NativeVoipSpec(reactCo
companion object {
private const val TAG = "RocketChat.VoipModule"
private const val EVENT_INITIAL_EVENTS = "VoipPushInitialEvents"
private const val EVENT_VOIP_ACCEPT_FAILED = "VoipAcceptFailed"

private var reactContextRef: WeakReference<ReactApplicationContext>? = null
private var initialEventsData: VoipPayload? = null
Expand Down Expand Up @@ -57,6 +58,30 @@ class VoipModule(reactContext: ReactApplicationContext) : NativeVoipSpec(reactCo
emitInitialEventsEvent(voipPayload)
}

/**
* Stash native accept failure for cold start [getInitialEvents] and emit [EVENT_VOIP_ACCEPT_FAILED] when JS is running.
*/
@JvmStatic
fun storeAcceptFailureForJs(payload: VoipPayload) {
val failed = payload.copy(voipAcceptFailed = true)
initialEventsData = failed
emitVoipAcceptFailedEvent(failed)
}

private fun emitVoipAcceptFailedEvent(voipPayload: VoipPayload) {
try {
reactContextRef?.get()?.let { context ->
if (context.hasActiveReactInstance()) {
context
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(EVENT_VOIP_ACCEPT_FAILED, voipPayload.toWritableMap())
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to emit VoipAcceptFailed", e)
}
}

@JvmStatic
fun clearInitialEventsInternal() {
try {
Expand Down
Loading
Loading