1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.network.telephony.ims
18 
19 import android.content.Context
20 import android.telephony.AccessNetworkConstants
21 import android.telephony.ims.ImsManager
22 import android.telephony.ims.ImsMmTelManager
23 import android.telephony.ims.ImsMmTelManager.WiFiCallingMode
24 import android.telephony.ims.ImsReasonInfo
25 import android.telephony.ims.ImsRegistrationAttributes
26 import android.telephony.ims.ImsStateCallback
27 import android.telephony.ims.RegistrationManager
28 import android.telephony.ims.feature.MmTelFeature
29 import android.util.Log
30 import kotlin.coroutines.resume
31 import kotlinx.coroutines.Dispatchers
32 import kotlinx.coroutines.asExecutor
33 import kotlinx.coroutines.channels.awaitClose
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.callbackFlow
36 import kotlinx.coroutines.flow.catch
37 import kotlinx.coroutines.flow.conflate
38 import kotlinx.coroutines.flow.flowOn
39 import kotlinx.coroutines.suspendCancellableCoroutine
40 import kotlinx.coroutines.withContext
41 
42 interface ImsMmTelRepository {
43     @WiFiCallingMode
44     fun getWiFiCallingMode(useRoamingMode: Boolean): Int
45 
46     fun imsRegisteredFlow(): Flow<Boolean>
47 
48     fun imsReadyFlow(): Flow<Boolean>
49 
50     suspend fun isSupported(
51         @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
52         @AccessNetworkConstants.TransportType transportType: Int,
53     ): Boolean
54 
55     suspend fun setCrossSimCallingEnabled(enabled: Boolean)
56 }
57 
58 class ImsMmTelRepositoryImpl(
59     context: Context,
60     private val subId: Int,
61     private val imsMmTelManager: ImsMmTelManager = ImsManager(context).getImsMmTelManager(subId),
62 ) : ImsMmTelRepository {
63 
64     @WiFiCallingMode
getWiFiCallingModenull65     override fun getWiFiCallingMode(useRoamingMode: Boolean): Int = try {
66         when {
67             !imsMmTelManager.isVoWiFiSettingEnabled -> ImsMmTelManager.WIFI_MODE_UNKNOWN
68             useRoamingMode -> imsMmTelManager.getVoWiFiRoamingModeSetting()
69             else -> imsMmTelManager.getVoWiFiModeSetting()
70         }
71     } catch (e: IllegalArgumentException) {
72         Log.w(TAG, "[$subId] getWiFiCallingMode failed useRoamingMode=$useRoamingMode", e)
73         ImsMmTelManager.WIFI_MODE_UNKNOWN
74     }
75 
<lambda>null76     override fun imsRegisteredFlow(): Flow<Boolean> = callbackFlow {
77         val callback = object : RegistrationManager.RegistrationCallback() {
78             override fun onRegistered(attributes: ImsRegistrationAttributes) {
79                 Log.d(TAG, "[$subId] IMS onRegistered")
80                 trySend(true)
81             }
82 
83             override fun onRegistering(imsTransportType: Int) {
84                 Log.d(TAG, "[$subId] IMS onRegistering")
85                 trySend(false)
86             }
87 
88             override fun onTechnologyChangeFailed(imsTransportType: Int, info: ImsReasonInfo) {
89                 Log.d(TAG, "[$subId] IMS onTechnologyChangeFailed")
90                 trySend(false)
91             }
92 
93             override fun onUnregistered(info: ImsReasonInfo) {
94                 Log.d(TAG, "[$subId] IMS onUnregistered")
95                 trySend(false)
96             }
97         }
98 
99         imsMmTelManager.registerImsRegistrationCallback(Dispatchers.Default.asExecutor(), callback)
100 
101         awaitClose { imsMmTelManager.unregisterImsRegistrationCallback(callback) }
102     }.catch { e ->
103         Log.w(TAG, "[$subId] error while imsRegisteredFlow", e)
104     }.conflate().flowOn(Dispatchers.Default)
105 
<lambda>null106     override fun imsReadyFlow(): Flow<Boolean> = callbackFlow {
107         val callback = object : ImsStateCallback() {
108             override fun onAvailable() {
109                 Log.d(TAG, "[$subId] IMS onAvailable")
110                 trySend(true)
111             }
112 
113             override fun onError() {
114                 Log.d(TAG, "[$subId] IMS onError")
115                 trySend(false)
116             }
117 
118             override fun onUnavailable(reason: Int) {
119                 Log.d(TAG, "[$subId] IMS onUnavailable")
120                 trySend(false)
121             }
122         }
123 
124         imsMmTelManager.registerImsStateCallback(Dispatchers.Default.asExecutor(), callback)
125 
126         awaitClose { imsMmTelManager.unregisterImsStateCallback(callback) }
127     }.catch { e ->
128         Log.w(TAG, "[$subId] error while imsReadyFlow", e)
129     }.conflate().flowOn(Dispatchers.Default)
130 
isSupportednull131     override suspend fun isSupported(
132         @MmTelFeature.MmTelCapabilities.MmTelCapability capability: Int,
133         @AccessNetworkConstants.TransportType transportType: Int,
134     ): Boolean = withContext(Dispatchers.Default) {
135         val logName = "isSupported(capability=$capability,transportType=$transportType)"
136         suspendCancellableCoroutine { continuation ->
137             try {
138                 imsMmTelManager.isSupported(
139                     capability,
140                     transportType,
141                     Dispatchers.Default.asExecutor(),
142                     continuation::resume,
143                 )
144             } catch (e: Exception) {
145                 continuation.resume(false)
146                 Log.w(TAG, "[$subId] $logName failed", e)
147             }
148         }.also { Log.d(TAG, "[$subId] $logName = $it") }
149     }
150 
setCrossSimCallingEnablednull151     override suspend fun setCrossSimCallingEnabled(enabled: Boolean) {
152         try {
153             imsMmTelManager.setCrossSimCallingEnabled(enabled)
154             Log.d(TAG, "[$subId] setCrossSimCallingEnabled: $enabled")
155         } catch (e: Exception) {
156             Log.e(TAG, "[$subId] failed setCrossSimCallingEnabled to $enabled", e)
157         }
158     }
159 
160     private companion object {
161         private const val TAG = "ImsMmTelRepository"
162     }
163 }
164