Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Element Call ringing notifications #2978

Merged
merged 24 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e5b1f75
Initial implementation
jmartinesp Jun 3, 2024
7f3a222
Try using `CallsManager` in case it makes the notification work as ex…
jmartinesp Jun 3, 2024
43bcb67
Got notification, full screen intent and missed call notifications wo…
jmartinesp Jun 4, 2024
00ef481
Add room avatars to the notifications
jmartinesp Jun 4, 2024
b9204b3
Add some docs
jmartinesp Jun 4, 2024
026459d
Start fixing tests
jmartinesp Jun 4, 2024
8c6de66
Merge remote-tracking branch 'origin/develop' into feature/jme/elemen…
jmartinesp Jun 4, 2024
d837cce
Merge remote-tracking branch 'origin/develop' into feature/jme/elemen…
jmartinesp Jun 5, 2024
f4b97cd
Fix lint issues
jmartinesp Jun 5, 2024
57129e7
Fix forbidden patterns issues
jmartinesp Jun 5, 2024
7a8a7c6
Update screenshots
Jun 5, 2024
4c6c3cd
Add missing tests
jmartinesp Jun 5, 2024
a80612c
Some cleanup
jmartinesp Jun 5, 2024
11aa0f2
Add changelog
jmartinesp Jun 5, 2024
291ee4e
Rework the incoming call screen
jmartinesp Jun 5, 2024
a5b3ac3
Update screenshots
Jun 5, 2024
c4d5e56
Merge branch 'develop' into feature/jme/element-call-ringing-with-tel…
jmartinesp Jun 5, 2024
09b20ff
Merge remote-tracking branch 'origin/develop' into feature/jme/elemen…
jmartinesp Jun 6, 2024
5eee0e1
Move `IncomingCallScreen` to its own file
jmartinesp Jun 6, 2024
126fdc5
Merge branch 'develop' into feature/jme/element-call-ringing-with-tel…
jmartinesp Jun 7, 2024
3235400
Make non-ringing notifications just messages added to the `MessagingS…
jmartinesp Jun 7, 2024
8c672c1
`IncomingCallForegroundService` is no longer a foreground service
jmartinesp Jun 7, 2024
c5be6d6
Remove `IncomingCallService`, fix tests
jmartinesp Jun 10, 2024
157ca22
Add `FLAG_SHOW_WHEN_LOCKED` to `ElementCallActivity` too
jmartinesp Jun 10, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,13 @@
package io.element.android.appconfig

object ElementCallConfig {
/**
* The default base URL for the Element Call service.
*/
const val DEFAULT_BASE_URL = "https://call.element.io"

/**
* The default duration of a ringing call in seconds before it's automatically dismissed.
*/
const val RINGING_CALL_DURATION_SECONDS = 15
}
1 change: 1 addition & 0 deletions changelog.d/2894.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ringing call notifications and full screen ringing screen for DMs when the device is locked.
31 changes: 31 additions & 0 deletions features/call/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id("io.element.android-library")
id("kotlin-parcelize")
}

android {
namespace = "io.element.android.features.call.api"
}

dependencies {
implementation(projects.anvilannotations)
implementation(projects.libraries.architecture)
implementation(projects.libraries.core)
implementation(projects.libraries.matrix.api)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 New Vector Ltd
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.element.android.features.call
package io.element.android.features.call.api

import android.os.Parcelable
import io.element.android.libraries.architecture.NodeInputs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.element.android.features.call.api

import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId

/**
* Entry point for the call feature.
*/
interface ElementCallEntryPoint {
/**
* Start a call of the given type.
* @param callType The type of call to start.
*/
fun startCall(callType: CallType)

/**
* Handle an incoming call.
* @param callType The type of call.
* @param eventId The event id of the event that started the call.
* @param senderId The user id of the sender of the event that started the call.
* @param roomName The name of the room the call is in.
* @param senderName The name of the sender of the event that started the call.
* @param avatarUrl The avatar url of the room or DM.
* @param timestamp The timestamp of the event that started the call.
* @param notificationChannelId The id of the notification channel to use for the call notification.
*/
fun handleIncomingCall(
callType: CallType.RoomCall,
eventId: EventId,
senderId: UserId,
roomName: String?,
senderName: String?,
avatarUrl: String?,
timestamp: Long,
notificationChannelId: String,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 New Vector Ltd
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,11 +23,15 @@ plugins {
}

android {
namespace = "io.element.android.features.call"
namespace = "io.element.android.features.call.impl"

buildFeatures {
buildConfig = true
}

testOptions {
unitTests.isIncludeAndroidResources = true
}
}

anvil {
Expand All @@ -41,22 +45,30 @@ dependencies {
implementation(projects.libraries.core)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrix.impl)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.network)
implementation(projects.libraries.preferences.api)
implementation(projects.libraries.push.api)
implementation(projects.libraries.uiStrings)
implementation(projects.services.analytics.api)
implementation(projects.services.toolbox.api)
implementation(libs.androidx.webkit)
implementation(libs.coil.compose)
implementation(libs.serialization.json)
api(projects.features.call.api)
ksp(libs.showkase.processor)

testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(libs.test.robolectric)
testImplementation(libs.test.mockk)
testImplementation(projects.features.call.test)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.push.test)
testImplementation(projects.services.analytics.test)
testImplementation(projects.tests.testutils)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@
android:name="android.hardware.microphone"
android:required="false" />

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<!-- Permissions for call foreground services -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />

<application>
<activity
Expand Down Expand Up @@ -70,10 +76,24 @@
</intent-filter>

</activity>

<activity android:name=".ui.IncomingCallActivity"
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
android:exported="false"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity="io.element.android.features.call" />

<service
android:name=".CallForegroundService"
android:name=".services.CallForegroundService"
android:enabled="true"
android:foregroundServiceType="mediaPlayback" />
android:exported="false"
android:foregroundServiceType="phoneCall" />

<receiver android:name=".receivers.DeclineCallBroadcastReceiver"
android:exported="false"
android:enabled="true" />

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.element.android.features.call.impl

import android.content.Context
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.call.api.CallType
import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.features.call.impl.notifications.CallNotificationData
import io.element.android.features.call.impl.utils.ActiveCallManager
import io.element.android.features.call.impl.utils.IntentProvider
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import javax.inject.Inject

@ContributesBinding(AppScope::class)
class DefaultElementCallEntryPoint @Inject constructor(
@ApplicationContext private val context: Context,
private val activeCallManager: ActiveCallManager,
) : ElementCallEntryPoint {
companion object {
const val EXTRA_CALL_TYPE = "EXTRA_CALL_TYPE"
const val REQUEST_CODE = 2255
}

override fun startCall(callType: CallType) {
context.startActivity(IntentProvider.createIntent(context, callType))
}

override fun handleIncomingCall(
callType: CallType.RoomCall,
eventId: EventId,
senderId: UserId,
roomName: String?,
senderName: String?,
avatarUrl: String?,
timestamp: Long,
notificationChannelId: String,
) {
val incomingCallNotificationData = CallNotificationData(
sessionId = callType.sessionId,
roomId = callType.roomId,
eventId = eventId,
senderId = senderId,
roomName = roomName,
senderName = senderName,
avatarUrl = avatarUrl,
timestamp = timestamp,
notificationChannelId = notificationChannelId,
)
activeCallManager.registerIncomingCall(notificationData = incomingCallNotificationData)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package io.element.android.features.call.data
package io.element.android.features.call.impl.data

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
* limitations under the License.
*/

package io.element.android.features.call.di
package io.element.android.features.call.impl.di

import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.call.ui.ElementCallActivity
import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver
import io.element.android.features.call.impl.ui.ElementCallActivity
import io.element.android.features.call.impl.ui.IncomingCallActivity
import io.element.android.libraries.di.AppScope

@ContributesTo(AppScope::class)
interface CallBindings {
fun inject(callActivity: ElementCallActivity)
fun inject(callActivity: IncomingCallActivity)
fun inject(declineCallBroadcastReceiver: DeclineCallBroadcastReceiver)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.element.android.features.call.impl.notifications

import android.os.Parcelable
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize

@Parcelize
data class CallNotificationData(
val sessionId: SessionId,
val roomId: RoomId,
val eventId: EventId,
val senderId: UserId,
val roomName: String?,
val senderName: String?,
val avatarUrl: String?,
val notificationChannelId: String,
val timestamp: Long,
) : Parcelable
Loading
Loading