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