1 /*
2  * Copyright 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 androidx.core.telecom
18 
19 import android.content.Intent
20 import android.os.Build
21 import android.os.IBinder
22 import android.telecom.Call
23 import android.telecom.InCallService
24 import android.util.Log
25 import androidx.annotation.CallSuper
26 import androidx.annotation.RequiresApi
27 import androidx.core.telecom.extensions.CallExtensionScope
28 import androidx.core.telecom.extensions.CallExtensionScopeImpl
29 import androidx.core.telecom.extensions.CallExtensions
30 import androidx.core.telecom.util.ExperimentalAppActions
31 import androidx.lifecycle.Lifecycle
32 import androidx.lifecycle.LifecycleOwner
33 import androidx.lifecycle.ServiceLifecycleDispatcher
34 import androidx.lifecycle.lifecycleScope
35 import kotlinx.coroutines.CoroutineScope
36 import kotlinx.coroutines.SupervisorJob
37 import kotlinx.coroutines.launch
38 
39 /**
40  * This class defines the Jetpack InCallService with the additional ability of defining a
41  * [LifecycleOwner]
42  */
43 @RequiresApi(Build.VERSION_CODES.O)
44 @Suppress("ContextNameSuffix")
45 public open class InCallServiceCompat : InCallService(), LifecycleOwner, CallExtensions {
46     // Since we define this service as a LifecycleOwner, we need to implement this dispatcher as
47     // well. See [LifecycleService] for the example used to implement [LifecycleOwner].
48     private val dispatcher = ServiceLifecycleDispatcher(this)
49 
50     private companion object {
51         private val TAG = InCallService::class.simpleName
52     }
53 
54     override val lifecycle: Lifecycle
55         get() = dispatcher.lifecycle
56 
57     @CallSuper
onCreatenull58     override fun onCreate() {
59         dispatcher.onServicePreSuperOnCreate()
60         super.onCreate()
61     }
62 
63     @CallSuper
64     @Suppress("InvalidNullabilityOverride")
onBindnull65     override fun onBind(intent: Intent?): IBinder? {
66         dispatcher.onServicePreSuperOnBind()
67         return super.onBind(intent)
68     }
69 
70     // We do not use onStart, but if the client does for some reason, we still want to override to
71     // ensure the lifecycle events are consistent.
72     @Deprecated("Deprecated in Java")
73     @Suppress("DEPRECATION")
74     @CallSuper
onStartnull75     override fun onStart(intent: Intent?, startId: Int) {
76         dispatcher.onServicePreSuperOnStart()
77         super.onStart(intent, startId)
78     }
79 
80     // We do not use onStartCommand, but if the client does for some reason, we still want to ensure
81     // that the super is called (this command internally calls onStart)
82     @CallSuper
onStartCommandnull83     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
84         return super.onStartCommand(intent, flags, startId)
85     }
86 
87     @CallSuper
onDestroynull88     override fun onDestroy() {
89         dispatcher.onServicePreSuperOnDestroy()
90         super.onDestroy()
91     }
92 
93     /**
94      * Connects extensions to the provided [Call], allowing the call to support additional optional
95      * behaviors beyond the traditional call state management.
96      *
97      * @param call The Call to connect extensions on.
98      * @param init The scope used to initialize and manage extensions in the scope of the Call.
99      * @see CallExtensions.connectExtensions
100      */
101     @ExperimentalAppActions
connectExtensionsnull102     override suspend fun connectExtensions(call: Call, init: CallExtensionScope.() -> Unit) {
103         // Attach this to the scope of the InCallService so it does not outlive its lifecycle
104         // Use a supervisor job to ensure that any exceptions that are encountered here do not kill
105         // the lifecycleScope.
106         CoroutineScope(lifecycleScope.coroutineContext + SupervisorJob())
107             .launch {
108                 val scope = CallExtensionScopeImpl(applicationContext, this, call)
109                 Log.v(TAG, "connectExtensions: calling init")
110                 scope.init()
111                 Log.v(TAG, "connectExtensions: connecting extensions")
112                 scope.connectExtensionSession()
113             }
114             .join()
115         Log.d(TAG, "connectExtensions: complete")
116     }
117 }
118