From ce14a04d41fd118150ea4b91279a79e7f6038787 Mon Sep 17 00:00:00 2001 From: Tarek Loubani Date: Thu, 11 Dec 2025 02:10:07 -0500 Subject: [PATCH 1/2] Implement persistent foreground service to keep calls active in background, with notification controls for managing the call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add CallForegroundService with persistent notification - Support calls in background without requiring picture-in-picture mode - Add "Return to call" and "End call" action buttons to CallForegroundService notification with corresponding PendingIntent - Handle proper foreground service types for microphone/camera permissions - Add notification permission and fallback messaging. - Add EndCallReceiver to handle end call broadcasts from notification action - Use existing ic_baseline_close_24 drawable for end call action icon - Register broadcast receiver in CallActivity to handle end call requests from notification using ReceiverFlag.NotExported for Android 14+ compatibility - Add proper cleanup flow: notification action → EndCallReceiver → CallActivity → proper hangup sequence - Track intentional call leaving to prevent unwanted service restarts - Release proximity sensor lock properly during notification-triggered hangup - Add diagnostic logging throughout the end call flow for debugging The implementation follows Android best practices: - Uses NotExported receiver flag for internal app-only broadcasts - Properly unregisters receivers in onDestroy to prevent leaks - Uses immutable PendingIntents for security - Maintains proper state management during call termination Signed-off-by: Tarek Loubani --- .vscode/settings.json | 3 + app/src/main/AndroidManifest.xml | 1 + .../nextcloud/talk/activities/CallActivity.kt | 162 +++++++++++++++++- .../talk/activities/CallBaseActivity.java | 19 +- .../talk/receivers/EndCallReceiver.kt | 36 ++++ .../talk/services/CallForegroundService.kt | 42 ++++- app/src/main/res/values/strings.xml | 3 + 7 files changed, 254 insertions(+), 12 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/src/main/java/com/nextcloud/talk/receivers/EndCallReceiver.kt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..c5f3f6b9c75 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ca34e3f2921..bc96ae54ea7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -280,6 +280,7 @@ + -> + // DEBUG: Log permission results + Log.d(TAG, "DEBUG: Permission request completed with results: $permissionMap") + val rationaleList: MutableList = ArrayList() val audioPermission = permissionMap[Manifest.permission.RECORD_AUDIO] if (audioPermission != null) { if (java.lang.Boolean.TRUE == audioPermission) { Log.d(TAG, "Microphone permission was granted") } else { + Log.d(TAG, "DEBUG: Microphone permission was denied") rationaleList.add(resources.getString(R.string.nc_microphone_permission_hint)) } } @@ -333,6 +340,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == cameraPermission) { Log.d(TAG, "Camera permission was granted") } else { + Log.d(TAG, "DEBUG: Camera permission was denied") rationaleList.add(resources.getString(R.string.nc_camera_permission_hint)) } } @@ -342,6 +350,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == bluetoothPermission) { enableBluetoothManager() } else { + Log.d(TAG, "DEBUG: Bluetooth permission was denied") // Only ask for bluetooth when already asking to grant microphone or camera access. Asking // for bluetooth solely is not important enough here and would most likely annoy the user. if (rationaleList.isNotEmpty()) { @@ -350,11 +359,32 @@ class CallActivity : CallBaseActivity() { } } } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val notificationPermission = permissionMap[Manifest.permission.POST_NOTIFICATIONS] + if (notificationPermission != null) { + if (java.lang.Boolean.TRUE == notificationPermission) { + Log.d(TAG, "Notification permission was granted") + } else { + Log.w(TAG, "DEBUG: Notification permission was denied - this may cause call hang") + rationaleList.add(resources.getString(R.string.nc_notification_permission_hint)) + } + } + } if (rationaleList.isNotEmpty()) { showRationaleDialogForSettings(rationaleList) } + // DEBUG: Check if we should proceed with call despite notification permission + val notificationPermissionGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissionMap[Manifest.permission.POST_NOTIFICATIONS] == true + } else { + true // Older Android versions have permission by default + } + + Log.d(TAG, "DEBUG: Notification permission granted: $notificationPermissionGranted, isConnectionEstablished: $isConnectionEstablished") + if (!isConnectionEstablished) { + Log.d(TAG, "DEBUG: Proceeding with prepareCall() despite notification permission status") prepareCall() } } @@ -383,6 +413,21 @@ class CallActivity : CallBaseActivity() { Log.d(TAG, "onCreate") super.onCreate(savedInstanceState) sharedApplication!!.componentApplication.inject(this) + + // Register broadcast receiver for ending call from notification + val endCallFilter = IntentFilter("com.nextcloud.talk.END_CALL_FROM_NOTIFICATION") + + // Use the proper utility function with ReceiverFlag for Android 14+ compatibility + // This receiver is for internal app use only (notification actions), so it should NOT be exported + registerPermissionHandlerBroadcastReceiver( + endCallFromNotificationReceiver, + endCallFilter, + permissionUtil!!.privateBroadcastPermission, + null, + ReceiverFlag.NotExported + ) + + Log.d(TAG, "Broadcast receiver registered successfully") callViewModel = ViewModelProvider(this, viewModelFactory)[CallViewModel::class.java] @@ -782,6 +827,7 @@ class CallActivity : CallBaseActivity() { true } binding!!.hangupButton.setOnClickListener { + isIntentionallyLeavingCall = true hangup(shutDownView = true, endCallForAll = true) } binding!!.endCallPopupMenu.setOnClickListener { @@ -796,6 +842,7 @@ class CallActivity : CallBaseActivity() { } } binding!!.hangupButton.setOnClickListener { + isIntentionallyLeavingCall = true hangup(shutDownView = true, endCallForAll = false) } binding!!.endCallPopupMenu.setOnClickListener { @@ -1022,6 +1069,18 @@ class CallActivity : CallBaseActivity() { permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT) } } + + // Check notification permission for Android 13+ (API 33+) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (permissionUtil!!.isPostNotificationsPermissionGranted()) { + Log.d(TAG, "Notification permission already granted") + } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { + permissionsToRequest.add(Manifest.permission.POST_NOTIFICATIONS) + rationaleList.add(resources.getString(R.string.nc_notification_permission_hint)) + } else { + permissionsToRequest.add(Manifest.permission.POST_NOTIFICATIONS) + } + } if (permissionsToRequest.isNotEmpty()) { if (rationaleList.isNotEmpty()) { @@ -1031,30 +1090,65 @@ class CallActivity : CallBaseActivity() { } } else if (!isConnectionEstablished) { prepareCall() + } else { + // DEBUG: All permissions granted but connection not established + Log.d(TAG, "DEBUG: All permissions granted but connection not established, proceeding with prepareCall()") + prepareCall() } } private fun prepareCall() { + Log.d(TAG, "DEBUG: prepareCall() started") basicInitialization() initViews() // updateSelfVideoViewPosition(true) checkRecordingConsentAndInitiateCall() + // Start foreground service only if we have notification permission (for Android 13+) + // or if we're on older Android versions where permission is automatically granted if (permissionUtil!!.isMicrophonePermissionGranted()) { - CallForegroundService.start(applicationContext, conversationName, intent.extras) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 13+ requires explicit notification permission + if (permissionUtil!!.isPostNotificationsPermissionGranted()) { + Log.d(TAG, "DEBUG: Starting foreground service with notification permission") + CallForegroundService.start(applicationContext, conversationName, intent.extras) + } else { + Log.w(TAG, "Notification permission not granted - call will work but without persistent notification") + // Show warning to user that notification permission is missing (10 seconds) + Snackbar.make( + binding!!.root, + resources.getString(R.string.nc_notification_permission_hint), + 10000 + ).show() + } + } else { + // Android 12 and below - notification permission is automatically granted + Log.d(TAG, "DEBUG: Starting foreground service (Android 12-)") + CallForegroundService.start(applicationContext, conversationName, intent.extras) + } + if (!microphoneOn) { onMicrophoneClick() } + } else { + Log.w(TAG, "DEBUG: Microphone permission not granted - skipping foreground service start") } + // The call should not hang just because notification permission was denied + // Always proceed with call setup regardless of notification permission + Log.d(TAG, "DEBUG: Ensuring call proceeds even without notification permission") + if (isVoiceOnlyCall) { binding!!.selfVideoViewWrapper.visibility = View.GONE } else if (permissionUtil!!.isCameraPermissionGranted()) { + Log.d(TAG, "DEBUG: Camera permission granted, showing video") binding!!.selfVideoViewWrapper.visibility = View.VISIBLE onCameraClick() if (cameraEnumerator!!.deviceNames.isEmpty()) { binding!!.cameraButton.visibility = View.GONE } + } else { + Log.w(TAG, "DEBUG: Camera permission not granted, hiding video") } } @@ -1071,13 +1165,31 @@ class CallActivity : CallBaseActivity() { for (rationale in rationaleList) { rationalesWithLineBreaks.append(rationale).append("\n\n") } + + // DEBUG: Log when permission rationale dialog is shown + Log.d(TAG, "DEBUG: Showing permission rationale dialog for permissions: $permissionsToRequest") + Log.d(TAG, "DEBUG: Rationale includes notification permission: ${permissionsToRequest.contains(Manifest.permission.POST_NOTIFICATIONS)}") + val dialogBuilder = MaterialAlertDialogBuilder(this) .setTitle(R.string.nc_permissions_rationale_dialog_title) .setMessage(rationalesWithLineBreaks) .setPositiveButton(R.string.nc_permissions_ask) { _, _ -> + Log.d(TAG, "DEBUG: User clicked 'Ask' for permissions") requestPermissionLauncher.launch(permissionsToRequest.toTypedArray()) } - .setNegativeButton(R.string.nc_common_dismiss, null) + .setNegativeButton(R.string.nc_common_dismiss) { _, _ -> + // DEBUG: Log when user dismisses permission request + Log.w(TAG, "DEBUG: User dismissed permission request for: $permissionsToRequest") + if (permissionsToRequest.contains(Manifest.permission.POST_NOTIFICATIONS)) { + Log.w(TAG, "DEBUG: Notification permission specifically dismissed - proceeding with call anyway") + } + + // Proceed with call even when notification permission is dismissed + if (!isConnectionEstablished) { + Log.d(TAG, "DEBUG: Proceeding with prepareCall() after dismissing notification permission") + prepareCall() + } + } viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder) dialogBuilder.show() } @@ -1401,6 +1513,10 @@ class CallActivity : CallBaseActivity() { } public override fun onDestroy() { + Log.d(TAG, "onDestroy called") + Log.d(TAG, "onDestroy: isIntentionallyLeavingCall=$isIntentionallyLeavingCall") + Log.d(TAG, "onDestroy: currentCallStatus=$currentCallStatus") + if (signalingMessageReceiver != null) { signalingMessageReceiver!!.removeListener(localParticipantMessageListener) signalingMessageReceiver!!.removeListener(offerMessageListener) @@ -1413,10 +1529,29 @@ class CallActivity : CallBaseActivity() { Log.d(TAG, "localStream is null") } if (currentCallStatus !== CallStatus.LEAVING) { - hangup(true, false) + // Only hangup if we're intentionally leaving + if (isIntentionallyLeavingCall) { + hangup(true, false) + } + } + // Only stop the foreground service if we're actually leaving the call + if (isIntentionallyLeavingCall || currentCallStatus === CallStatus.LEAVING) { + CallForegroundService.stop(applicationContext) } - CallForegroundService.stop(applicationContext) + + Log.d(TAG, "onDestroy: Releasing proximity sensor - updating to IDLE state") powerManagerUtils!!.updatePhoneState(PowerManagerUtils.PhoneState.IDLE) + Log.d(TAG, "onDestroy: Proximity sensor released") + + // Unregister receiver + try { + Log.d(TAG, "Unregistering endCallFromNotificationReceiver...") + unregisterReceiver(endCallFromNotificationReceiver) + Log.d(TAG, "endCallFromNotificationReceiver unregistered successfully") + } catch (e: Exception) { + Log.w(TAG, "Failed to unregister endCallFromNotificationReceiver", e) + } + super.onDestroy() } @@ -1989,7 +2124,10 @@ class CallActivity : CallBaseActivity() { } private fun hangup(shutDownView: Boolean, endCallForAll: Boolean) { - Log.d(TAG, "hangup! shutDownView=$shutDownView") + Log.d(TAG, "hangup! shutDownView=$shutDownView, endCallForAll=$endCallForAll") + Log.d(TAG, "hangup! isIntentionallyLeavingCall=$isIntentionallyLeavingCall") + Log.d(TAG, "hangup! powerManagerUtils state before cleanup: ${powerManagerUtils != null}") + if (shutDownView) { setCallState(CallStatus.LEAVING) } @@ -3163,4 +3301,18 @@ class CallActivity : CallBaseActivity() { private const val SESSION_ID_PREFFIX_END: Int = 4 } + + // Broadcast receiver to handle end call from notification + private val endCallFromNotificationReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == "com.nextcloud.talk.END_CALL_FROM_NOTIFICATION") { + Log.d(TAG, "Received end call from notification broadcast") + Log.d(TAG, "endCallFromNotificationReceiver: Setting isIntentionallyLeavingCall=true") + isIntentionallyLeavingCall = true + Log.d(TAG, "endCallFromNotificationReceiver: Releasing proximity sensor before hangup") + powerManagerUtils?.updatePhoneState(PowerManagerUtils.PhoneState.IDLE) + hangup(shutDownView = true, endCallForAll = false) + } + } + } } diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java index 45fd67a5872..28f20ba6d8e 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java @@ -39,6 +39,9 @@ public abstract class CallBaseActivity extends BaseActivity { public void handleOnBackPressed() { if (isPipModePossible()) { enterPipMode(); + } else { + // Move the task to background instead of finishing + moveTaskToBack(true); } } }; @@ -98,8 +101,13 @@ void enableKeyguard() { @Override public void onStop() { super.onStop(); - if (shouldFinishOnStop()) { - finish(); + // Don't automatically finish when going to background + // Only finish if explicitly leaving the call + if (shouldFinishOnStop() && !isChangingConfigurations()) { + // Check if we're really leaving the call or just backgrounding + if (isFinishing()) { + finish(); + } } } @@ -124,10 +132,9 @@ void enterPipMode() { mPictureInPictureParamsBuilder.setAspectRatio(pipRatio); enterPictureInPictureMode(mPictureInPictureParamsBuilder.build()); } else { - // we don't support other solutions than PIP to have a call in the background. - // If PIP is not available the call is ended when user presses the home button. - Log.d(TAG, "Activity was finished because PIP is not available."); - finish(); + // If PIP is not available, move to background instead of finishing + Log.d(TAG, "PIP is not available, moving call to background."); + moveTaskToBack(true); } } diff --git a/app/src/main/java/com/nextcloud/talk/receivers/EndCallReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/EndCallReceiver.kt new file mode 100644 index 00000000000..4d6f23945b2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/receivers/EndCallReceiver.kt @@ -0,0 +1,36 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.nextcloud.talk.activities.CallActivity +import com.nextcloud.talk.services.CallForegroundService + +class EndCallReceiver : BroadcastReceiver() { + companion object { + private const val TAG = "EndCallReceiver" + } + + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == "com.nextcloud.talk.END_CALL") { + Log.d(TAG, "Received end call broadcast") + + // Stop the foreground service + context?.let { + CallForegroundService.stop(it) + + // Send broadcast to CallActivity to end the call + val endCallIntent = Intent("com.nextcloud.talk.END_CALL_FROM_NOTIFICATION") + endCallIntent.setPackage(context.packageName) + context.sendBroadcast(endCallIntent) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/services/CallForegroundService.kt b/app/src/main/java/com/nextcloud/talk/services/CallForegroundService.kt index f6a53d84871..c9a288f9b65 100644 --- a/app/src/main/java/com/nextcloud/talk/services/CallForegroundService.kt +++ b/app/src/main/java/com/nextcloud/talk/services/CallForegroundService.kt @@ -15,12 +15,14 @@ import android.content.pm.ServiceInfo import android.os.Build import android.os.Bundle import android.os.IBinder +import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE import androidx.core.content.ContextCompat import com.nextcloud.talk.R import com.nextcloud.talk.activities.CallActivity import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.receivers.EndCallReceiver import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO @@ -57,6 +59,26 @@ class CallForegroundService : Service() { val contentTitle = conversationName?.takeIf { it.isNotBlank() } ?: getString(R.string.nc_call_ongoing_notification_default_title) val pendingIntent = createContentIntent(callExtras) + + // Create action to return to call + val returnToCallAction = NotificationCompat.Action.Builder( + R.drawable.ic_call_white_24dp, + getString(R.string.nc_call_ongoing_notification_return_action), + pendingIntent + ).build() + + // Create action to end call + val endCallPendingIntent = createEndCallIntent(callExtras) + + // DIAGNOSTIC: Logging icon resource availability + Log.d("CallForegroundService", "Creating end call action - checking icon resources") + Log.d("CallForegroundService", "Using ic_baseline_close_24 instead of non-existent ic_close_white_24px") + + val endCallAction = NotificationCompat.Action.Builder( + R.drawable.ic_baseline_close_24, // DIAGNOSTIC: Fixed - using existing icon + getString(R.string.nc_call_ongoing_notification_end_action), + endCallPendingIntent + ).build() // Already has parentheses, good! return NotificationCompat.Builder(this, channelId) .setContentTitle(contentTitle) @@ -69,6 +91,9 @@ class CallForegroundService : Service() { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setContentIntent(pendingIntent) .setShowWhen(false) + .addAction(returnToCallAction) + .addAction(endCallAction) + .setAutoCancel(false) .build() } @@ -79,13 +104,28 @@ class CallForegroundService : Service() { private fun createContentIntent(callExtras: Bundle?): PendingIntent { val intent = Intent(this, CallActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT callExtras?.let { putExtras(Bundle(it)) } } val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE return PendingIntent.getActivity(this, 0, intent, flags) } + + private fun createEndCallIntent(callExtras: Bundle?): PendingIntent { + // DIAGNOSTIC: Logging intent creation + Log.d("CallForegroundService", "Creating EndCallIntent with EndCallReceiver class") + + val intent = Intent(this, EndCallReceiver::class.java).apply { + action = "com.nextcloud.talk.END_CALL" + callExtras?.let { putExtras(Bundle(it)) } + } + + Log.d("CallForegroundService", "EndCallIntent created successfully with action: ${intent.action}") + + val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + return PendingIntent.getBroadcast(this, 1, intent, flags) + } private fun resolveForegroundServiceType(callExtras: Bundle?): Int { var serviceType = 0 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c15d936afc..306fb0ad0d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -299,6 +299,7 @@ How to translate with transifex: To enable video communication please grant \"Camera\" permission. To enable voice communication please grant \"Microphone\" permission. To enable bluetooth speakers please grant \"Nearby devices\" permission. + To show call notifications and keep calls active in the background, please grant \"Notifications\" permission. Microphone is enabled and audio is recording @@ -320,6 +321,8 @@ How to translate with transifex: You missed a call from %s Call in progress Tap to return to your call. + Return to call + End call Open picture-in-picture mode Change audio output Toggle camera From b65b98554a737419ecbf8d2690b543c9f448d95d Mon Sep 17 00:00:00 2001 From: Tarek Loubani Date: Sun, 21 Dec 2025 04:15:29 -0500 Subject: [PATCH 2/2] Make logging of warnings and errors more consistent with repository style Signed-off-by: Tarek Loubani --- .../nextcloud/talk/activities/CallActivity.kt | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt index 03533c7d9c5..41974e19655 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt @@ -322,8 +322,8 @@ class CallActivity : CallBaseActivity() { private var requestPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { permissionMap: Map -> - // DEBUG: Log permission results - Log.d(TAG, "DEBUG: Permission request completed with results: $permissionMap") + // Log permission results + Log.d(TAG, "Permission request completed with results: $permissionMap") val rationaleList: MutableList = ArrayList() val audioPermission = permissionMap[Manifest.permission.RECORD_AUDIO] @@ -331,7 +331,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == audioPermission) { Log.d(TAG, "Microphone permission was granted") } else { - Log.d(TAG, "DEBUG: Microphone permission was denied") + Log.d(TAG, "Microphone permission was denied") rationaleList.add(resources.getString(R.string.nc_microphone_permission_hint)) } } @@ -340,7 +340,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == cameraPermission) { Log.d(TAG, "Camera permission was granted") } else { - Log.d(TAG, "DEBUG: Camera permission was denied") + Log.d(TAG, "Camera permission was denied") rationaleList.add(resources.getString(R.string.nc_camera_permission_hint)) } } @@ -350,7 +350,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == bluetoothPermission) { enableBluetoothManager() } else { - Log.d(TAG, "DEBUG: Bluetooth permission was denied") + Log.d(TAG, "Bluetooth permission was denied") // Only ask for bluetooth when already asking to grant microphone or camera access. Asking // for bluetooth solely is not important enough here and would most likely annoy the user. if (rationaleList.isNotEmpty()) { @@ -365,7 +365,7 @@ class CallActivity : CallBaseActivity() { if (java.lang.Boolean.TRUE == notificationPermission) { Log.d(TAG, "Notification permission was granted") } else { - Log.w(TAG, "DEBUG: Notification permission was denied - this may cause call hang") + Log.w(TAG, "Notification permission was denied - this may cause call hang") rationaleList.add(resources.getString(R.string.nc_notification_permission_hint)) } } @@ -374,17 +374,17 @@ class CallActivity : CallBaseActivity() { showRationaleDialogForSettings(rationaleList) } - // DEBUG: Check if we should proceed with call despite notification permission + // Check if we should proceed with call despite notification permission val notificationPermissionGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissionMap[Manifest.permission.POST_NOTIFICATIONS] == true } else { true // Older Android versions have permission by default } - Log.d(TAG, "DEBUG: Notification permission granted: $notificationPermissionGranted, isConnectionEstablished: $isConnectionEstablished") + Log.d(TAG, "DEBUGNotification permission granted: $notificationPermissionGranted, isConnectionEstablished: $isConnectionEstablished") if (!isConnectionEstablished) { - Log.d(TAG, "DEBUG: Proceeding with prepareCall() despite notification permission status") + Log.d(TAG, "Proceeding with prepareCall() despite notification permission status") prepareCall() } } @@ -1091,14 +1091,14 @@ class CallActivity : CallBaseActivity() { } else if (!isConnectionEstablished) { prepareCall() } else { - // DEBUG: All permissions granted but connection not established - Log.d(TAG, "DEBUG: All permissions granted but connection not established, proceeding with prepareCall()") + // All permissions granted but connection not established + Log.d(TAG, "All permissions granted but connection not established, proceeding with prepareCall()") prepareCall() } } private fun prepareCall() { - Log.d(TAG, "DEBUG: prepareCall() started") + Log.d(TAG, "prepareCall() started") basicInitialization() initViews() // updateSelfVideoViewPosition(true) @@ -1110,7 +1110,7 @@ class CallActivity : CallBaseActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13+ requires explicit notification permission if (permissionUtil!!.isPostNotificationsPermissionGranted()) { - Log.d(TAG, "DEBUG: Starting foreground service with notification permission") + Log.d(TAG, "Starting foreground service with notification permission") CallForegroundService.start(applicationContext, conversationName, intent.extras) } else { Log.w(TAG, "Notification permission not granted - call will work but without persistent notification") @@ -1123,7 +1123,7 @@ class CallActivity : CallBaseActivity() { } } else { // Android 12 and below - notification permission is automatically granted - Log.d(TAG, "DEBUG: Starting foreground service (Android 12-)") + Log.d(TAG, "Starting foreground service (Android 12-)") CallForegroundService.start(applicationContext, conversationName, intent.extras) } @@ -1131,24 +1131,24 @@ class CallActivity : CallBaseActivity() { onMicrophoneClick() } } else { - Log.w(TAG, "DEBUG: Microphone permission not granted - skipping foreground service start") + Log.w(TAG, "Microphone permission not granted - skipping foreground service start") } // The call should not hang just because notification permission was denied // Always proceed with call setup regardless of notification permission - Log.d(TAG, "DEBUG: Ensuring call proceeds even without notification permission") + Log.d(TAG, "Ensuring call proceeds even without notification permission") if (isVoiceOnlyCall) { binding!!.selfVideoViewWrapper.visibility = View.GONE } else if (permissionUtil!!.isCameraPermissionGranted()) { - Log.d(TAG, "DEBUG: Camera permission granted, showing video") + Log.d(TAG, "Camera permission granted, showing video") binding!!.selfVideoViewWrapper.visibility = View.VISIBLE onCameraClick() if (cameraEnumerator!!.deviceNames.isEmpty()) { binding!!.cameraButton.visibility = View.GONE } } else { - Log.w(TAG, "DEBUG: Camera permission not granted, hiding video") + Log.w(TAG, "Camera permission not granted, hiding video") } } @@ -1166,27 +1166,27 @@ class CallActivity : CallBaseActivity() { rationalesWithLineBreaks.append(rationale).append("\n\n") } - // DEBUG: Log when permission rationale dialog is shown - Log.d(TAG, "DEBUG: Showing permission rationale dialog for permissions: $permissionsToRequest") - Log.d(TAG, "DEBUG: Rationale includes notification permission: ${permissionsToRequest.contains(Manifest.permission.POST_NOTIFICATIONS)}") + // Log when permission rationale dialog is shown + Log.d(TAG, "Showing permission rationale dialog for permissions: $permissionsToRequest") + Log.d(TAG, "Rationale includes notification permission: ${permissionsToRequest.contains(Manifest.permission.POST_NOTIFICATIONS)}") val dialogBuilder = MaterialAlertDialogBuilder(this) .setTitle(R.string.nc_permissions_rationale_dialog_title) .setMessage(rationalesWithLineBreaks) .setPositiveButton(R.string.nc_permissions_ask) { _, _ -> - Log.d(TAG, "DEBUG: User clicked 'Ask' for permissions") + Log.d(TAG, "User clicked 'Ask' for permissions") requestPermissionLauncher.launch(permissionsToRequest.toTypedArray()) } .setNegativeButton(R.string.nc_common_dismiss) { _, _ -> - // DEBUG: Log when user dismisses permission request - Log.w(TAG, "DEBUG: User dismissed permission request for: $permissionsToRequest") + // Log when user dismisses permission request + Log.w(TAG, "User dismissed permission request for: $permissionsToRequest") if (permissionsToRequest.contains(Manifest.permission.POST_NOTIFICATIONS)) { - Log.w(TAG, "DEBUG: Notification permission specifically dismissed - proceeding with call anyway") + Log.w(TAG, "Notification permission specifically dismissed - proceeding with call anyway") } // Proceed with call even when notification permission is dismissed if (!isConnectionEstablished) { - Log.d(TAG, "DEBUG: Proceeding with prepareCall() after dismissing notification permission") + Log.d(TAG, "Proceeding with prepareCall() after dismissing notification permission") prepareCall() } }