1 /* <lambda>null2 * Copyright 2023 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.privacysandbox.sdkruntime.core.controller 18 19 import android.app.sdksandbox.sdkprovider.SdkSandboxController 20 import android.content.Context 21 import android.os.Build 22 import android.os.Bundle 23 import android.os.IBinder 24 import androidx.annotation.Keep 25 import androidx.annotation.RestrictTo 26 import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP 27 import androidx.privacysandbox.sdkruntime.core.AppOwnedSdkSandboxInterfaceCompat 28 import androidx.privacysandbox.sdkruntime.core.LoadSdkCompatException 29 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkCompat 30 import androidx.privacysandbox.sdkruntime.core.SandboxedSdkProviderCompat 31 import androidx.privacysandbox.sdkruntime.core.SdkSandboxClientImportanceListenerCompat 32 import androidx.privacysandbox.sdkruntime.core.Versions 33 import androidx.privacysandbox.sdkruntime.core.activity.SdkSandboxActivityHandlerCompat 34 import androidx.privacysandbox.sdkruntime.core.controller.impl.LocalImpl 35 import androidx.privacysandbox.sdkruntime.core.controller.impl.PlatformUDCImpl 36 import java.util.concurrent.Executor 37 import java.util.concurrent.atomic.AtomicBoolean 38 import kotlin.coroutines.Continuation 39 import kotlin.coroutines.resume 40 import kotlin.coroutines.resumeWithException 41 import kotlinx.coroutines.suspendCancellableCoroutine 42 import org.jetbrains.annotations.TestOnly 43 44 /** 45 * Compat version of [android.app.sdksandbox.sdkprovider.SdkSandboxController]. 46 * 47 * Controller that is used by SDK loaded in the sandbox or locally to access information provided by 48 * the sandbox environment. 49 * 50 * It enables the SDK to communicate with other SDKS and know about the state of the sdks that are 51 * currently loaded. 52 * 53 * An instance can be obtained using [SdkSandboxControllerCompat.from]. The [Context] can be 54 * obtained using [SandboxedSdkProviderCompat.context]. 55 * 56 * @see [android.app.sdksandbox.sdkprovider.SdkSandboxController] 57 */ 58 class SdkSandboxControllerCompat 59 internal constructor(private val controllerImpl: SandboxControllerImpl) { 60 61 /** 62 * Load SDK in a SDK sandbox java process or locally. 63 * 64 * The caller may only load SDKs the client app depends on into the SDK sandbox. 65 * 66 * @param sdkName name of the SDK to be loaded. 67 * @param params additional parameters to be passed to the SDK in the form of a [Bundle] as 68 * agreed between the client and the SDK. 69 * @return [SandboxedSdkCompat] from SDK on a successful run. 70 * @throws [LoadSdkCompatException] on fail. 71 */ 72 suspend fun loadSdk(sdkName: String, params: Bundle): SandboxedSdkCompat = 73 suspendCancellableCoroutine { continuation -> 74 controllerImpl.loadSdk( 75 sdkName, 76 params, 77 Runnable::run, 78 ContinuationLoadSdkCallback(continuation) 79 ) 80 } 81 82 /** 83 * Fetches information about Sdks that are loaded in the sandbox or locally. 84 * 85 * @return List of [SandboxedSdkCompat] containing all currently loaded sdks 86 * @see [android.app.sdksandbox.sdkprovider.SdkSandboxController.getSandboxedSdks] 87 */ 88 fun getSandboxedSdks(): List<SandboxedSdkCompat> = controllerImpl.getSandboxedSdks() 89 90 /** 91 * Fetches all [AppOwnedSdkSandboxInterfaceCompat] that are registered by the app. 92 * 93 * @return List of all currently registered [AppOwnedSdkSandboxInterfaceCompat] 94 */ 95 fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> = 96 controllerImpl.getAppOwnedSdkSandboxInterfaces() 97 98 /** 99 * Returns an identifier for a [SdkSandboxActivityHandlerCompat] after registering it. 100 * 101 * This function registers an implementation of [SdkSandboxActivityHandlerCompat] created by an 102 * SDK and returns an [IBinder] which uniquely identifies the passed 103 * [SdkSandboxActivityHandlerCompat] object. 104 * 105 * @param handlerCompat is the [SdkSandboxActivityHandlerCompat] to register 106 * @return [IBinder] uniquely identify the passed [SdkSandboxActivityHandlerCompat] 107 * @see SdkSandboxController.registerSdkSandboxActivityHandler 108 */ 109 fun registerSdkSandboxActivityHandler(handlerCompat: SdkSandboxActivityHandlerCompat): IBinder = 110 controllerImpl.registerSdkSandboxActivityHandler(handlerCompat) 111 112 /** 113 * Registers a listener to be notified of changes in the client's 114 * [android.app.ActivityManager.RunningAppProcessInfo.importance]. 115 * 116 * @param executor Executor for running listenerCompat 117 * @param listenerCompat an implementation of [SdkSandboxClientImportanceListenerCompat] to 118 * register. 119 */ 120 fun registerSdkSandboxClientImportanceListener( 121 executor: Executor, 122 listenerCompat: SdkSandboxClientImportanceListenerCompat 123 ) = controllerImpl.registerSdkSandboxClientImportanceListener(executor, listenerCompat) 124 125 /** 126 * Unregisters a listener previously registered using 127 * [registerSdkSandboxClientImportanceListener] 128 * 129 * @param listenerCompat an implementation of [SdkSandboxClientImportanceListenerCompat] to 130 * unregister. 131 */ 132 fun unregisterSdkSandboxClientImportanceListener( 133 listenerCompat: SdkSandboxClientImportanceListenerCompat 134 ) = controllerImpl.unregisterSdkSandboxClientImportanceListener(listenerCompat) 135 136 /** 137 * Unregister an already registered [SdkSandboxActivityHandlerCompat]. 138 * 139 * If the passed [SdkSandboxActivityHandlerCompat] is registered, it will be unregistered. 140 * Otherwise, it will do nothing. 141 * 142 * If the [IBinder] token of the unregistered handler used to start a [android.app.Activity], 143 * the [android.app.Activity] will fail to start. 144 * 145 * @param handlerCompat is the [SdkSandboxActivityHandlerCompat] to unregister. 146 * @see SdkSandboxController.unregisterSdkSandboxActivityHandler 147 */ 148 fun unregisterSdkSandboxActivityHandler(handlerCompat: SdkSandboxActivityHandlerCompat) = 149 controllerImpl.unregisterSdkSandboxActivityHandler(handlerCompat) 150 151 /** 152 * Returns the package name of the client app. 153 * 154 * @return Package name of the client app. 155 */ 156 fun getClientPackageName(): String = controllerImpl.getClientPackageName() 157 158 @RestrictTo(LIBRARY_GROUP) 159 interface SandboxControllerImpl { 160 161 fun loadSdk(sdkName: String, params: Bundle, executor: Executor, callback: LoadSdkCallback) 162 163 fun getSandboxedSdks(): List<SandboxedSdkCompat> 164 165 fun getAppOwnedSdkSandboxInterfaces(): List<AppOwnedSdkSandboxInterfaceCompat> 166 167 fun registerSdkSandboxActivityHandler( 168 handlerCompat: SdkSandboxActivityHandlerCompat 169 ): IBinder 170 171 fun unregisterSdkSandboxActivityHandler(handlerCompat: SdkSandboxActivityHandlerCompat) 172 173 fun getClientPackageName(): String 174 175 fun registerSdkSandboxClientImportanceListener( 176 executor: Executor, 177 listenerCompat: SdkSandboxClientImportanceListenerCompat 178 ) 179 180 fun unregisterSdkSandboxClientImportanceListener( 181 listenerCompat: SdkSandboxClientImportanceListenerCompat 182 ) 183 } 184 185 companion object { 186 187 private var localImpl: SandboxControllerImpl? = null 188 189 /** 190 * Creates [SdkSandboxControllerCompat]. 191 * 192 * @param context SDK context 193 * @return SdkSandboxControllerCompat object. 194 */ 195 @JvmStatic 196 fun from(context: Context): SdkSandboxControllerCompat { 197 val clientVersion = Versions.CLIENT_VERSION 198 if (clientVersion != null) { 199 val implFromClient = 200 localImpl 201 ?: throw UnsupportedOperationException( 202 "Shouldn't happen: No controller implementation available" 203 ) 204 return SdkSandboxControllerCompat(LocalImpl(implFromClient, clientVersion)) 205 } 206 val platformImpl = PlatformImplFactory.create(context) 207 return SdkSandboxControllerCompat(platformImpl) 208 } 209 210 /** 211 * Inject implementation from client library. Implementation will be used only if loaded 212 * locally. This method will be called from client side via reflection during loading SDK. 213 */ 214 @JvmStatic 215 @Keep 216 @RestrictTo(LIBRARY_GROUP) 217 fun injectLocalImpl(impl: SandboxControllerImpl) { 218 check(localImpl == null) { "Local implementation already injected" } 219 localImpl = impl 220 } 221 222 @TestOnly 223 @RestrictTo(LIBRARY_GROUP) 224 fun resetLocalImpl() { 225 localImpl = null 226 } 227 } 228 229 private object PlatformImplFactory { 230 fun create(context: Context): SandboxControllerImpl { 231 if (Build.VERSION.SDK_INT >= 34) { 232 return PlatformUDCImpl.from(context) 233 } 234 throw UnsupportedOperationException("SDK should be loaded locally on API below 34") 235 } 236 } 237 238 private class ContinuationLoadSdkCallback( 239 private val continuation: Continuation<SandboxedSdkCompat> 240 ) : LoadSdkCallback, AtomicBoolean(false) { 241 override fun onResult(result: SandboxedSdkCompat) { 242 // Do not attempt to resume more than once, even if the caller is buggy. 243 if (compareAndSet(false, true)) { 244 continuation.resume(result) 245 } 246 } 247 248 override fun onError(error: LoadSdkCompatException) { 249 // Do not attempt to resume more than once, even if the caller is buggy. 250 if (compareAndSet(false, true)) { 251 continuation.resumeWithException(error) 252 } 253 } 254 255 override fun toString() = "ContinuationLoadSdkCallback(outcomeReceived = ${get()})" 256 } 257 } 258