BinderAdapterDelegate.kt

/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
@file:RequiresApi(Build.VERSION_CODES.TIRAMISU)
@file:JvmName("SandboxedUiAdapterProxy")

package androidx.privacysandbox.ui.provider

import android.content.Context
import android.content.res.Configuration
import android.hardware.display.DisplayManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.view.SurfaceControlViewHost
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.privacysandbox.ui.core.IRemoteSessionClient
import androidx.privacysandbox.ui.core.IRemoteSessionController
import androidx.privacysandbox.ui.core.ISandboxedUiAdapter
import androidx.privacysandbox.ui.core.SandboxedUiAdapter
import java.util.concurrent.Executor

/**
 * Provides a [Bundle] containing a Binder which represents a [SandboxedUiAdapter]. The Bundle
 * is shuttled to the host app in order for the [SandboxedUiAdapter] to be used to retrieve
 * content.
 */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun SandboxedUiAdapter.toCoreLibInfo(@Suppress("ContextFirst") context: Context): Bundle {
    val binderAdapter = BinderAdapterDelegate(context, this)
    // TODO: Add version info
    val bundle = Bundle()
    // Bundle key is a binary compatibility requirement
    bundle.putBinder("uiAdapterBinder", binderAdapter)
    return bundle
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private class BinderAdapterDelegate(
    private val sandboxContext: Context,
    private val adapter: SandboxedUiAdapter
) : ISandboxedUiAdapter.Stub() {

    fun openSession(
        context: Context,
        initialWidth: Int,
        initialHeight: Int,
        isZOrderOnTop: Boolean,
        clientExecutor: Executor,
        client: SandboxedUiAdapter.SessionClient
    ) {
        adapter.openSession(
            context, initialWidth, initialHeight, isZOrderOnTop, clientExecutor,
            client
        )
    }

    override fun openRemoteSession(
        hostToken: IBinder,
        displayId: Int,
        initialWidth: Int,
        initialHeight: Int,
        isZOrderOnTop: Boolean,
        remoteSessionClient: IRemoteSessionClient
    ) {
        val mHandler = Handler(Looper.getMainLooper())
        mHandler.post {
            try {
                val mDisplayManager: DisplayManager =
                    sandboxContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
                val windowContext =
                    sandboxContext.createDisplayContext(mDisplayManager.getDisplay(displayId))
                val surfaceControlViewHost = SurfaceControlViewHost(
                    windowContext,
                    mDisplayManager.getDisplay(displayId), hostToken
                )
                val sessionClient = SessionClientProxy(
                    surfaceControlViewHost, initialWidth, initialHeight, remoteSessionClient
                )
                openSession(
                    windowContext, initialWidth, initialHeight, isZOrderOnTop,
                    Runnable::run, sessionClient
                )
            } catch (exception: Throwable) {
                remoteSessionClient.onRemoteSessionError(exception.message)
            }
        }
    }

    private inner class SessionClientProxy(
        private val surfaceControlViewHost: SurfaceControlViewHost,
        private val initialWidth: Int,
        private val initialHeight: Int,
        private val remoteSessionClient: IRemoteSessionClient
    ) : SandboxedUiAdapter.SessionClient {

        override fun onSessionOpened(session: SandboxedUiAdapter.Session) {
            val view = session.view
            surfaceControlViewHost.setView(view, initialWidth, initialHeight)
            val surfacePackage = surfaceControlViewHost.surfacePackage
            val remoteSessionController =
                RemoteSessionController(surfaceControlViewHost, session)
            remoteSessionClient.onRemoteSessionOpened(
                surfacePackage, remoteSessionController,
                /* isZOrderOnTop= */ true
            )
        }

        override fun onSessionError(throwable: Throwable) {
            remoteSessionClient.onRemoteSessionError(throwable.message)
        }

        override fun onResizeRequested(width: Int, height: Int) {
            remoteSessionClient.onResizeRequested(width, height)
        }

        @VisibleForTesting
        private inner class RemoteSessionController(
            val surfaceControlViewHost: SurfaceControlViewHost,
            val session: SandboxedUiAdapter.Session
        ) : IRemoteSessionController.Stub() {

            override fun notifyConfigurationChanged(configuration: Configuration) {
                surfaceControlViewHost.surfacePackage?.notifyConfigurationChanged(configuration)
                session.notifyConfigurationChanged(configuration)
            }

            override fun notifyResized(width: Int, height: Int) {
                surfaceControlViewHost.relayout(width, height)
                session.notifyResized(width, height)
            }

            override fun close() {
                session.close()
                surfaceControlViewHost.release()
            }
        }
    }
}