diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5a6918f1..f6e23bab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ # limitations under the License. # [versions] -agp = "8.9.1" +agp = "8.13.2" fragmentCompose = "1.8.6" kotlin = "2.1.10" coreKtx = "1.17.0" @@ -66,6 +66,7 @@ playServicesMlkitBarcodeScanning = "18.3.1" protobuf = "0.9.4" firebaseCrashlyticsBuildtools = "3.0.5" uwb = "1.0.0-alpha10" +telecom = "1.1.0-alpha04" [libraries] @@ -99,6 +100,7 @@ coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil-video = { module = "io.coil-kt:coil-video", version.ref = "coil" } kotlin-coroutines-play = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "coroutines" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "play-services-location" } +androidx-core-telecom = { module = "androidx.core:core-telecom", version.ref = "telecom" } # Core dependencies android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "agp" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e2847c82..37f853b1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/samples/connectivity/telecom/build.gradle.kts b/samples/connectivity/telecom/build.gradle.kts index 098e282e..a198e264 100644 --- a/samples/connectivity/telecom/build.gradle.kts +++ b/samples/connectivity/telecom/build.gradle.kts @@ -25,11 +25,14 @@ plugins { android { namespace = "com.example.platform.connectivity.telecom" - compileSdk = 36 + compileSdk { + version = release(version = 36) { + minorApiLevel = 1 + } + } defaultConfig { minSdk = 23 - targetSdk = 35 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -39,7 +42,6 @@ android { } dependencies { - implementation("androidx.core:core-telecom:1.0.1") implementation(project(mapOf("path" to ":samples:connectivity:audio"))) implementation(libs.androidx.activity.compose) @@ -50,6 +52,9 @@ dependencies { implementation(libs.androidx.material3) implementation(project(":shared")) + // Sample specific dependencies + implementation(libs.androidx.core.telecom) + implementation(libs.accompanist.permissions) androidTestImplementation(libs.androidx.test.core) diff --git a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/TelecomCallSample.kt b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/TelecomCallSample.kt index ccd50a6b..30b3e94c 100644 --- a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/TelecomCallSample.kt +++ b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/TelecomCallSample.kt @@ -25,16 +25,21 @@ import android.widget.Toast import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -44,6 +49,7 @@ import com.example.platform.connectivity.telecom.call.TelecomCallService import com.example.platform.connectivity.telecom.model.TelecomCall import com.example.platform.connectivity.telecom.model.TelecomCallRepository import com.example.platform.shared.PermissionBox +import androidx.core.net.toUri @RequiresApi(Build.VERSION_CODES.O) @Composable @@ -104,6 +110,21 @@ private fun TelecomCallOptions() { "No active call" } Text(text = title, style = MaterialTheme.typography.titleLarge) + + var excludeCallLogging by rememberSaveable { + mutableStateOf(false) + } + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = excludeCallLogging, + onCheckedChange = { isChecked -> + excludeCallLogging = isChecked + } + ) + Text("Exclude from call logs") + } Button( enabled = !hasOngoingCall, onClick = { @@ -111,7 +132,8 @@ private fun TelecomCallOptions() { context.launchCall( action = TelecomCallService.ACTION_INCOMING_CALL, name = "Alice", - uri = Uri.parse("tel:12345"), + uri = "tel:12345".toUri(), + excludeCallLogging = excludeCallLogging ) }, ) { @@ -123,7 +145,8 @@ private fun TelecomCallOptions() { context.launchCall( action = TelecomCallService.ACTION_OUTGOING_CALL, name = "Bob", - uri = Uri.parse("tel:54321"), + uri = "tel:54321".toUri(), + excludeCallLogging = excludeCallLogging ) }, ) { @@ -133,12 +156,13 @@ private fun TelecomCallOptions() { } @RequiresApi(Build.VERSION_CODES.O) -private fun Context.launchCall(action: String, name: String, uri: Uri) { +internal fun Context.launchCall(action: String, name: String, uri: Uri, excludeCallLogging: Boolean) { startService( Intent(this, TelecomCallService::class.java).apply { this.action = action putExtra(TelecomCallService.EXTRA_NAME, name) putExtra(TelecomCallService.EXTRA_URI, uri) + putExtra(TelecomCallService.EXTRA_EXCLUDE_CALL_LOGGING, excludeCallLogging) }, ) } diff --git a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallActivity.kt b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallActivity.kt index 0da07851..abfb6c58 100644 --- a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallActivity.kt +++ b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallActivity.kt @@ -20,6 +20,7 @@ import android.app.KeyguardManager import android.content.Intent import android.os.Build import android.os.Bundle +import android.telecom.TelecomManager import android.util.Log import android.view.WindowManager import androidx.activity.ComponentActivity @@ -31,6 +32,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import androidx.core.content.getSystemService +import androidx.core.net.toUri +import com.example.platform.connectivity.telecom.launchCall import com.example.platform.connectivity.telecom.model.TelecomCallRepository @@ -50,6 +53,8 @@ class TelecomCallActivity : ComponentActivity() { // Set the right flags for a call type activity. setupCallActivity() + handleCallBack() + setContent { MaterialTheme { Surface( @@ -78,6 +83,12 @@ class TelecomCallActivity : ComponentActivity() { ) } + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + handleCallBack() + } + /** * Enable the calling activity to be shown in the lockscreen and dismiss the keyguard to enable * users to answer without unblocking. @@ -94,8 +105,17 @@ class TelecomCallActivity : ComponentActivity() { } val keyguardManager = getSystemService() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && keyguardManager != null) { - keyguardManager.requestDismissKeyguard(this, null) + keyguardManager?.requestDismissKeyguard(this, null) + } + + private fun handleCallBack() { + if (intent.action == TelecomManager.ACTION_CALL_BACK) { + launchCall( + action = TelecomCallService.ACTION_OUTGOING_CALL, + name = "Bob", + uri = "tel:54321".toUri(), + excludeCallLogging = false + ) } } } diff --git a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallService.kt b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallService.kt index d894c467..cd2a311f 100644 --- a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallService.kt +++ b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/call/TelecomCallService.kt @@ -59,6 +59,7 @@ class TelecomCallService : Service() { companion object { internal const val EXTRA_NAME: String = "extra_name" internal const val EXTRA_URI: String = "extra_uri" + internal const val EXTRA_EXCLUDE_CALL_LOGGING = "extra_exclude_call_logging" internal const val ACTION_INCOMING_CALL = "incoming_call" internal const val ACTION_OUTGOING_CALL = "outgoing_call" internal const val ACTION_UPDATE_CALL = "update_call" @@ -124,6 +125,7 @@ class TelecomCallService : Service() { @Suppress("DEPRECATION") intent.getParcelableExtra(EXTRA_URI)!! } + val excludeCallLogging = intent.getBooleanExtra(EXTRA_EXCLUDE_CALL_LOGGING, false) scope.launch { if (incoming) { @@ -133,7 +135,7 @@ class TelecomCallService : Service() { launch { // Register the call with the Telecom stack - telecomRepository.registerCall(name, uri, incoming) + telecomRepository.registerCall(name, uri, excludeCallLogging, incoming) } if (!incoming) { diff --git a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/model/TelecomCallRepository.kt b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/model/TelecomCallRepository.kt index 8c56e715..350cc3b7 100644 --- a/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/model/TelecomCallRepository.kt +++ b/samples/connectivity/telecom/src/main/java/com/example/platform/connectivity/telecom/model/TelecomCallRepository.kt @@ -25,11 +25,7 @@ import androidx.annotation.RequiresApi import androidx.core.telecom.CallAttributesCompat import androidx.core.telecom.CallControlResult import androidx.core.telecom.CallControlScope -import androidx.core.telecom.CallException import androidx.core.telecom.CallsManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -87,8 +83,13 @@ class TelecomCallRepository(private val callsManager: CallsManager) { * Register a new call with the provided attributes. * Use the [currentCall] StateFlow to receive status updates and process call related actions. */ - suspend fun registerCall(displayName: String, address: Uri, isIncoming: Boolean) { - // For simplicity we don't support multiple calls + suspend fun registerCall( + displayName: String, + address: Uri, + excludeCallLogging: Boolean, + isIncoming: Boolean + ) { + // For simplicity, we don't support multiple calls check(_currentCall.value !is TelecomCall.Registered) { "There cannot be more than one call at the same time." } @@ -97,6 +98,7 @@ class TelecomCallRepository(private val callsManager: CallsManager) { val attributes = CallAttributesCompat( displayName = displayName, address = address, + isLogExcluded = excludeCallLogging, direction = if (isIncoming) { CallAttributesCompat.DIRECTION_INCOMING } else { @@ -221,7 +223,7 @@ class TelecomCallRepository(private val callsManager: CallsManager) { } is TelecomCallAction.ToggleMute -> { - // We cannot programmatically mute the telecom stack. Instead we just update + // We cannot programmatically mute the telecom stack. Instead, we just update // the state of the call and this will start/stop audio capturing. updateCurrentCall { copy(isMuted = !isMuted) diff --git a/settings.gradle.kts b/settings.gradle.kts index 76de2e01..af8fc634 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0" +} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories {