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