Skip to content

Commit dcde0be

Browse files
committed
Stop calls from ringing after answering.
Switched call notification to NotificationCompat.CallStyle. This adds answer/decline buttons to the notification so that users can answer incoming calls. Adds caller avatar to notification. Signed-off-by: Jens Zalzala <jens@shakingearthdigital.com>
1 parent 1b2d6e3 commit dcde0be

File tree

5 files changed

+102
-6
lines changed

5 files changed

+102
-6
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@
293293

294294
<receiver android:name=".receivers.DirectReplyReceiver" />
295295
<receiver android:name=".receivers.MarkAsReadReceiver" />
296+
<receiver android:name=".receivers.CallNotificationActionReceiver" android:exported="false" />
296297
<receiver android:name=".receivers.DismissRecordingAvailableReceiver" />
297298
<receiver android:name=".receivers.ShareRecordingToChatReceiver" />
298299

app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import android.view.OrientationEventListener
4141
import android.view.View
4242
import android.view.View.OnTouchListener
4343
import androidx.activity.result.contract.ActivityResultContracts
44+
import androidx.core.app.NotificationManagerCompat
4445
import androidx.annotation.DrawableRes
4546
import androidx.appcompat.app.AlertDialog
4647
import androidx.compose.material3.MaterialTheme
@@ -118,6 +119,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION
118119
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
119120
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
120121
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL
122+
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP
121123
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_BREAKOUT_ROOM
122124
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_MODERATOR
123125
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MODIFIED_BASE_URL
@@ -553,6 +555,11 @@ class CallActivity : CallBaseActivity() {
553555

554556
if (extras.containsKey(KEY_FROM_NOTIFICATION_START_CALL)) {
555557
isIncomingCallFromNotification = extras.getBoolean(KEY_FROM_NOTIFICATION_START_CALL)
558+
val notificationId = extras.getInt(KEY_NOTIFICATION_TIMESTAMP, 0)
559+
if (notificationId != 0) {
560+
// cancel the notification to stop the call ringing
561+
NotificationManagerCompat.from(this).cancel(notificationId)
562+
}
556563
}
557564
if (extras.containsKey(KEY_IS_BREAKOUT_ROOM)) {
558565
isBreakoutRoom = extras.getBoolean(KEY_IS_BREAKOUT_ROOM)

app/src/main/java/com/nextcloud/talk/activities/CallBaseActivity.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import android.annotation.SuppressLint;
1010
import android.app.AppOpsManager;
11-
import android.app.KeyguardManager;
1211
import android.app.PictureInPictureParams;
1312
import android.content.Context;
1413
import android.content.pm.PackageManager;
@@ -76,10 +75,7 @@ void dismissKeyguard() {
7675
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
7776
setShowWhenLocked(true);
7877
setTurnScreenOn(true);
79-
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
80-
keyguardManager.requestDismissKeyguard(this, null);
8178
} else {
82-
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
8379
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
8480
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
8581
}

app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import coil.request.ImageRequest
4545
import com.bluelinelabs.logansquare.LoganSquare
4646
import com.nextcloud.talk.BuildConfig
4747
import com.nextcloud.talk.R
48+
import com.nextcloud.talk.activities.CallActivity
4849
import com.nextcloud.talk.activities.MainActivity
4950
import com.nextcloud.talk.api.NcApi
5051
import com.nextcloud.talk.application.NextcloudTalkApplication
@@ -61,6 +62,7 @@ import com.nextcloud.talk.models.json.participants.Participant
6162
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
6263
import com.nextcloud.talk.models.json.push.DecryptedPushMessage
6364
import com.nextcloud.talk.models.json.push.NotificationUser
65+
import com.nextcloud.talk.receivers.CallNotificationActionReceiver
6466
import com.nextcloud.talk.receivers.DirectReplyReceiver
6567
import com.nextcloud.talk.receivers.DismissRecordingAvailableReceiver
6668
import com.nextcloud.talk.receivers.MarkAsReadReceiver
@@ -95,7 +97,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
9597
import com.nextcloud.talk.utils.preferences.AppPreferences
9698
import io.reactivex.Observable
9799
import io.reactivex.Observer
98-
import io.reactivex.android.schedulers.AndroidSchedulers
99100
import io.reactivex.disposables.Disposable
100101
import io.reactivex.schedulers.Schedulers
101102
import okhttp3.JavaNetCookieJar
@@ -268,11 +269,66 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
268269
}
269270
)
270271

272+
val pendingIntentFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
273+
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
274+
} else {
275+
PendingIntent.FLAG_UPDATE_CURRENT
276+
}
277+
278+
val answerVoiceBundle = Bundle(bundle).apply { putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, true) }
279+
val answerVoicePendingIntent = PendingIntent.getActivity(
280+
applicationContext,
281+
requestCode + ANSWER_VOICE_REQUEST_OFFSET,
282+
Intent(applicationContext, CallActivity::class.java).apply {
283+
putExtras(answerVoiceBundle)
284+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
285+
},
286+
pendingIntentFlags
287+
)
288+
289+
val answerVideoBundle = Bundle(bundle).apply { putBoolean(BundleKeys.KEY_CALL_VOICE_ONLY, false) }
290+
val answerVideoPendingIntent = PendingIntent.getActivity(
291+
applicationContext,
292+
requestCode + ANSWER_VIDEO_REQUEST_OFFSET,
293+
Intent(applicationContext, CallActivity::class.java).apply {
294+
putExtras(answerVideoBundle)
295+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
296+
},
297+
pendingIntentFlags
298+
)
299+
300+
val declinePendingIntent = PendingIntent.getBroadcast(
301+
applicationContext,
302+
requestCode + DECLINE_CALL_REQUEST_OFFSET,
303+
Intent(applicationContext, CallNotificationActionReceiver::class.java).apply {
304+
action = CallNotificationActionReceiver.ACTION_DECLINE_CALL
305+
putExtra(KEY_NOTIFICATION_TIMESTAMP, pushMessage.timestamp.toInt())
306+
},
307+
pendingIntentFlags
308+
)
309+
271310
val soundUri = getCallRingtoneUri(applicationContext, appPreferences)
272311
val notificationChannelId = NotificationUtils.NotificationChannels.NOTIFICATION_CHANNEL_CALLS_V4.name
273312
val uri = signatureVerification.user!!.baseUrl!!.toUri()
274313
val baseUrl = uri.host
275314

315+
val callerPersonBuilder = Person.Builder()
316+
.setName(conversation.displayName)
317+
.setImportant(true)
318+
if (conversation.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
319+
val avatarUrl = ApiUtils.getUrlForAvatar(
320+
signatureVerification.user!!.baseUrl!!,
321+
conversation.name,
322+
false,
323+
darkMode = DisplayUtils.isDarkModeOn(applicationContext)
324+
)
325+
loadAvatarSync(avatarUrl, applicationContext)?.let { callerPersonBuilder.setIcon(it) }
326+
}
327+
val callerPerson = callerPersonBuilder.build()
328+
329+
val isVideoCall = (conversation.callFlag and Participant.InCallFlags.WITH_VIDEO) > 0
330+
val primaryAnswerIntent = if (isVideoCall) answerVideoPendingIntent else answerVoicePendingIntent
331+
276332
val notification =
277333
NotificationCompat.Builder(applicationContext, notificationChannelId)
278334
.setPriority(NotificationCompat.PRIORITY_HIGH)
@@ -289,6 +345,11 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
289345
.setContentIntent(fullScreenPendingIntent)
290346
.setFullScreenIntent(fullScreenPendingIntent, true)
291347
.setSound(soundUri)
348+
.setStyle(
349+
NotificationCompat.CallStyle
350+
.forIncomingCall(callerPerson, declinePendingIntent, primaryAnswerIntent)
351+
.setIsVideo(isVideoCall)
352+
)
292353
.build()
293354
notification.flags = notification.flags or Notification.FLAG_INSISTENT
294355

@@ -299,7 +360,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
299360

300361
chatNetworkDataSource?.getRoom(userBeingCalled, roomToken = pushMessage.id!!)
301362
?.subscribeOn(Schedulers.io())
302-
?.observeOn(AndroidSchedulers.mainThread())
363+
?.observeOn(Schedulers.io())
303364
?.subscribe(object : Observer<ConversationModel> {
304365
override fun onSubscribe(d: Disposable) {
305366
// unused atm
@@ -1107,5 +1168,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
11071168
private const val TIMER_COUNT = 12
11081169
private const val TIMER_DELAY: Long = 5
11091170
private const val LINEBREAK: String = "\n"
1171+
private const val ANSWER_VOICE_REQUEST_OFFSET = 1
1172+
private const val ANSWER_VIDEO_REQUEST_OFFSET = 2
1173+
private const val DECLINE_CALL_REQUEST_OFFSET = 3
11101174
}
11111175
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
*
5+
* SPDX-FileCopyrightText: 2026 Jens Zalzala <jens@shakingearthdigital.com>
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: GPL-3.0-or-later
8+
*
9+
*/
10+
package com.nextcloud.talk.receivers
11+
12+
import android.content.BroadcastReceiver
13+
import android.content.Context
14+
import android.content.Intent
15+
import androidx.core.app.NotificationManagerCompat
16+
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_NOTIFICATION_TIMESTAMP
17+
18+
class CallNotificationActionReceiver : BroadcastReceiver() {
19+
20+
override fun onReceive(context: Context, intent: Intent) {
21+
val notificationId = intent.getIntExtra(KEY_NOTIFICATION_TIMESTAMP, 0)
22+
NotificationManagerCompat.from(context).cancel(notificationId)
23+
}
24+
25+
companion object {
26+
const val ACTION_DECLINE_CALL = "com.nextcloud.talk.ACTION_DECLINE_CALL"
27+
}
28+
}

0 commit comments

Comments
 (0)