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.appfunctions.internal
18 
19 import android.content.Context
20 import android.os.Build
21 import android.os.CancellationSignal
22 import android.os.OutcomeReceiver
23 import androidx.annotation.RequiresApi
24 import androidx.appfunctions.AppFunctionException
25 import androidx.appfunctions.AppFunctionFunctionNotFoundException
26 import androidx.appfunctions.AppFunctionManagerCompat
27 import androidx.appfunctions.AppFunctionManagerCompat.Companion.APP_FUNCTION_STATE_DEFAULT
28 import androidx.appfunctions.AppFunctionManagerCompat.Companion.APP_FUNCTION_STATE_DISABLED
29 import androidx.appfunctions.AppFunctionManagerCompat.Companion.APP_FUNCTION_STATE_ENABLED
30 import androidx.appfunctions.AppFunctionSystemUnknownException
31 import androidx.appfunctions.ExecuteAppFunctionRequest
32 import androidx.appfunctions.ExecuteAppFunctionResponse
33 import com.android.extensions.appfunctions.AppFunctionManager
34 import kotlin.coroutines.resume
35 import kotlin.coroutines.resumeWithException
36 import kotlinx.coroutines.Runnable
37 import kotlinx.coroutines.suspendCancellableCoroutine
38 
39 /** Provides the AppFunctionManager backend through the sidecar extension. */
40 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
41 internal class ExtensionAppFunctionManagerApi(private val context: Context) :
42     AppFunctionManagerApi {
43 
44     private val appFunctionManager: AppFunctionManager by lazy { AppFunctionManager(context) }
45 
46     override suspend fun executeAppFunction(
47         request: ExecuteAppFunctionRequest,
48     ): ExecuteAppFunctionResponse {
49         return suspendCancellableCoroutine { cont ->
50             val cancellationSignal = CancellationSignal()
51             cont.invokeOnCancellation { cancellationSignal.cancel() }
52             appFunctionManager.executeAppFunction(
53                 request.toPlatformExtensionClass(),
54                 Runnable::run,
55                 cancellationSignal,
56                 object :
57                     OutcomeReceiver<
58                         com.android.extensions.appfunctions.ExecuteAppFunctionResponse,
59                         com.android.extensions.appfunctions.AppFunctionException
60                     > {
61                     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
62                     override fun onResult(
63                         result: com.android.extensions.appfunctions.ExecuteAppFunctionResponse
64                     ) {
65                         cont.resume(
66                             ExecuteAppFunctionResponse.Success.fromPlatformExtensionClass(result)
67                         )
68                     }
69 
70                     override fun onError(
71                         error: com.android.extensions.appfunctions.AppFunctionException
72                     ) {
73                         val exception =
74                             fixAppFunctionExceptionErrorType(
75                                 AppFunctionException.fromPlatformExtensionsClass(error)
76                             )
77                         cont.resume(ExecuteAppFunctionResponse.Error(exception))
78                     }
79                 }
80             )
81         }
82     }
83 
84     override suspend fun isAppFunctionEnabled(packageName: String, functionId: String): Boolean {
85         return suspendCancellableCoroutine { cont ->
86             appFunctionManager.isAppFunctionEnabled(
87                 functionId,
88                 packageName,
89                 Runnable::run,
90                 object : OutcomeReceiver<Boolean, Exception> {
91                     override fun onResult(result: Boolean?) {
92                         if (result == null) {
93                             cont.resumeWithException(IllegalStateException("Something went wrong"))
94                         } else {
95                             cont.resume(result)
96                         }
97                     }
98 
99                     override fun onError(error: Exception) {
100                         cont.resumeWithException(error)
101                     }
102                 }
103             )
104         }
105     }
106 
107     override suspend fun setAppFunctionEnabled(
108         functionId: String,
109         @AppFunctionManagerCompat.EnabledState newEnabledState: Int,
110     ) {
111         val platformExtensionEnabledState = convertToPlatformExtensionEnabledState(newEnabledState)
112         return suspendCancellableCoroutine { cont ->
113             appFunctionManager.setAppFunctionEnabled(
114                 functionId,
115                 platformExtensionEnabledState,
116                 Runnable::run,
117                 object : OutcomeReceiver<Void, Exception> {
118                     override fun onResult(result: Void?) {
119                         cont.resume(Unit)
120                     }
121 
122                     override fun onError(error: Exception) {
123                         cont.resumeWithException(error)
124                     }
125                 }
126             )
127         }
128     }
129 
130     private fun fixAppFunctionExceptionErrorType(
131         exception: AppFunctionException
132     ): AppFunctionException {
133         // Platform throws IllegalArgumentException when function not found during function
134         // execution and ends up being AppFunctionSystemUnknownException instead of
135         // AppFunctionFunctionNotFoundException.
136         // The bug was already shipped in some Android version, so we have to keep this fix.
137         if (
138             exception is AppFunctionSystemUnknownException &&
139                 exception.errorMessage == "IllegalArgumentException: App function not found."
140         ) {
141             return AppFunctionFunctionNotFoundException("App function not found.")
142         }
143         return exception
144     }
145 
146     @AppFunctionManager.EnabledState
147     private fun convertToPlatformExtensionEnabledState(
148         @AppFunctionManagerCompat.EnabledState enabledState: Int
149     ): Int {
150         return when (enabledState) {
151             APP_FUNCTION_STATE_DEFAULT -> AppFunctionManager.APP_FUNCTION_STATE_DEFAULT
152             APP_FUNCTION_STATE_ENABLED -> AppFunctionManager.APP_FUNCTION_STATE_ENABLED
153             APP_FUNCTION_STATE_DISABLED -> AppFunctionManager.APP_FUNCTION_STATE_DISABLED
154             else -> throw IllegalArgumentException("Unknown enabled state $enabledState")
155         }
156     }
157 }
158