EmbeddingCompat.kt

/*
 * Copyright 2021 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.
 */

package androidx.window.embedding

import androidx.window.extensions.embedding.SplitInfo as OEMSplitInfo
import android.app.Activity
import android.content.Context
import android.util.Log
import androidx.window.core.BuildConfig
import androidx.window.core.ConsumerAdapter
import androidx.window.core.ExperimentalWindowApi
import androidx.window.core.ExtensionsUtil
import androidx.window.core.VerificationMode
import androidx.window.embedding.EmbeddingInterfaceCompat.EmbeddingCallbackInterface
import androidx.window.embedding.SplitController.SplitSupportStatus.Companion.SPLIT_AVAILABLE
import androidx.window.extensions.WindowExtensions.VENDOR_API_LEVEL_2
import androidx.window.extensions.WindowExtensionsProvider
import androidx.window.extensions.core.util.function.Consumer
import androidx.window.extensions.embedding.ActivityEmbeddingComponent
import java.lang.reflect.Proxy

/**
 * Adapter implementation for different historical versions of activity embedding OEM interface in
 * [ActivityEmbeddingComponent]. Only supports the single current version in this implementation.
 */
internal class EmbeddingCompat constructor(
    private val embeddingExtension: ActivityEmbeddingComponent,
    private val adapter: EmbeddingAdapter,
    private val consumerAdapter: ConsumerAdapter,
    private val applicationContext: Context
) : EmbeddingInterfaceCompat {

    override fun setRules(rules: Set<EmbeddingRule>) {
        var hasSplitRule = false
        for (rule in rules) {
            if (rule is SplitRule) {
                hasSplitRule = true
                break
            }
        }
        if (hasSplitRule &&
            SplitController.getInstance(applicationContext).splitSupportStatus != SPLIT_AVAILABLE
        ) {
            if (BuildConfig.verificationMode == VerificationMode.LOG) {
                Log.w(
                    TAG, "Cannot set SplitRule because ActivityEmbedding Split is not " +
                        "supported or PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED is not set."
                )
            }
            return
        }

        val r = adapter.translate(applicationContext, rules)
        embeddingExtension.setEmbeddingRules(r)
    }

    override fun setEmbeddingCallback(embeddingCallback: EmbeddingCallbackInterface) {
        if (ExtensionsUtil.safeVendorApiLevel < VENDOR_API_LEVEL_2) {
            consumerAdapter.addConsumer(
                embeddingExtension,
                List::class,
                "setSplitInfoCallback"
            ) { values ->
                val splitInfoList = values.filterIsInstance<OEMSplitInfo>()
                embeddingCallback.onSplitInfoChanged(adapter.translate(splitInfoList))
            }
        } else {
            val callback = Consumer<List<OEMSplitInfo>> { splitInfoList ->
                embeddingCallback.onSplitInfoChanged(adapter.translate(splitInfoList))
            }
            embeddingExtension.setSplitInfoCallback(callback)
        }
    }

    override fun isActivityEmbedded(activity: Activity): Boolean {
        return embeddingExtension.isActivityEmbedded(activity)
    }

    @ExperimentalWindowApi
    override fun setSplitAttributesCalculator(
        calculator: (SplitAttributesCalculatorParams) -> SplitAttributes
    ) {
        if (!isSplitAttributesCalculatorSupported()) {
            throw UnsupportedOperationException("#setSplitAttributesCalculator is not supported " +
                "on the device.")
        }
        embeddingExtension.setSplitAttributesCalculator(
            adapter.translateSplitAttributesCalculator(calculator)
        )
    }

    override fun clearSplitAttributesCalculator() {
        if (!isSplitAttributesCalculatorSupported()) {
            throw UnsupportedOperationException("#clearSplitAttributesCalculator is not " +
                "supported on the device.")
        }
        embeddingExtension.clearSplitAttributesCalculator()
    }

    override fun isSplitAttributesCalculatorSupported(): Boolean =
        ExtensionsUtil.safeVendorApiLevel >= VENDOR_API_LEVEL_2

    companion object {
        const val DEBUG = true
        private const val TAG = "EmbeddingCompat"

        fun isEmbeddingAvailable(): Boolean {
            return try {
                EmbeddingCompat::class.java.classLoader?.let { loader ->
                    SafeActivityEmbeddingComponentProvider(
                        loader,
                        ConsumerAdapter(loader),
                        WindowExtensionsProvider.getWindowExtensions(),
                    ).activityEmbeddingComponent != null
                } ?: false
            } catch (e: NoClassDefFoundError) {
                if (DEBUG) {
                    Log.d(TAG, "Embedding extension version not found")
                }
                false
            } catch (e: UnsupportedOperationException) {
                if (DEBUG) {
                    Log.d(TAG, "Stub Extension")
                }
                false
            }
        }

        fun embeddingComponent(): ActivityEmbeddingComponent {
            return if (isEmbeddingAvailable()) {
                EmbeddingCompat::class.java.classLoader?.let { loader ->
                    SafeActivityEmbeddingComponentProvider(
                        loader,
                        ConsumerAdapter(loader),
                        WindowExtensionsProvider.getWindowExtensions(),
                    ).activityEmbeddingComponent
                } ?: emptyActivityEmbeddingProxy()
            } else {
                emptyActivityEmbeddingProxy()
            }
        }

        private fun emptyActivityEmbeddingProxy(): ActivityEmbeddingComponent {
            return Proxy.newProxyInstance(
                EmbeddingCompat::class.java.classLoader,
                arrayOf(ActivityEmbeddingComponent::class.java)
            ) { _, _, _ -> } as ActivityEmbeddingComponent
        }
    }
}