1 /*
<lambda>null2  * Copyright 2025 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 androidx.core.telecom.extensions
18 
19 import android.content.Context
20 import android.net.Uri
21 import android.util.Log
22 import androidx.core.telecom.internal.CallIconStateListener
23 import androidx.core.telecom.internal.CapabilityExchangeListenerRemote
24 import androidx.core.telecom.util.ExperimentalAppActions
25 import kotlin.coroutines.resume
26 import kotlin.properties.Delegates
27 import kotlinx.coroutines.CoroutineScope
28 import kotlinx.coroutines.launch
29 import kotlinx.coroutines.suspendCancellableCoroutine
30 
31 /**
32  * Remote implementation of the [CallIconExtensionRemote] interface.
33  *
34  * This class handles communication with the remote call icon extension, receiving updates to the
35  * call icon URI and providing them to the application through a flow.
36  *
37  * @param context The Android context.
38  * @param callScope The coroutine scope for launching flows.
39  */
40 @ExperimentalAppActions
41 internal class CallIconExtensionRemoteImpl(
42     private val context: Context,
43     private val callScope: CoroutineScope,
44     private val onCallIconChanged: suspend (Uri) -> Unit
45 ) : CallIconExtensionRemote {
46 
47     /** Indicates whether the remote call icon extension is supported. */
48     override var isSupported by Delegates.notNull<Boolean>()
49 
50     companion object {
51         /** The tag used for logging. */
52         val TAG: String = CallIconExtensionRemoteImpl::class.java.simpleName
53     }
54 
55     /** Returns an empty array of actions, as no actions are supported. */
56     internal val actions
57         get() = IntArray(0) // No actions supported
58 
59     /**
60      * Called when the capability exchange is complete.
61      *
62      * This method determines whether the remote extension is supported based on the negotiated
63      * capability and connects to the remote actions interface if supported.
64      *
65      * @param negotiatedCapability The negotiated capability.
66      * @param remote The remote capability exchange listener.
67      */
68     internal suspend fun onExchangeComplete(
69         negotiatedCapability: Capability?,
70         remote: CapabilityExchangeListenerRemote?
71     ) {
72         if (negotiatedCapability == null || remote == null) {
73             Log.i(TAG, "onNegotiated: remote is not capable")
74             isSupported = false
75             return
76         }
77 
78         isSupported = true
79         connectToRemote(negotiatedCapability, remote)
80     }
81 
82     /**
83      * Connects to the remote call icon actions interface.
84      *
85      * This method establishes communication with the remote extension using the provided capability
86      * and listener. It sets up a listener to receive call icon URI updates and resumes the
87      * continuation with the remote actions interface.
88      *
89      * @param negotiatedCapability The negotiated capability.
90      * @param remote The remote capability exchange listener.
91      */
92     private suspend fun connectToRemote(
93         negotiatedCapability: Capability,
94         remote: CapabilityExchangeListenerRemote
95     ): Unit = suspendCancellableCoroutine { continuation ->
96         val stateListener =
97             CallIconStateListener(
98                 callIconUriUpdater = {
99                     callScope.launch {
100                         // Called when the remote extension updates the URI.
101                         onCallIconChanged(it)
102                     }
103                 },
104                 finishSync = { callScope.launch { continuation.resume(Unit) } }
105             )
106         remote.onCreateCallIconExtension(
107             negotiatedCapability.featureVersion,
108             negotiatedCapability.supportedActions,
109             context.packageName,
110             stateListener
111         )
112     }
113 }
114