ExtensionsWindowLayoutInfoAdapter.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.layout.adapter.extensions

import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
import android.app.Activity
import android.content.Context
import android.os.Build
import androidx.annotation.UiContext
import androidx.window.core.Bounds
import androidx.window.layout.FoldingFeature
import androidx.window.layout.FoldingFeature.State.Companion.FLAT
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.layout.HardwareFoldingFeature
import androidx.window.layout.HardwareFoldingFeature.Type.Companion.FOLD
import androidx.window.layout.HardwareFoldingFeature.Type.Companion.HINGE
import androidx.window.layout.WindowLayoutInfo
import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculatorCompat.computeCurrentWindowMetrics

internal object ExtensionsWindowLayoutInfoAdapter {

    internal fun translate(
        windowMetrics: WindowMetrics,
        oemFeature: OEMFoldingFeature,
    ): FoldingFeature? {
        val type = when (oemFeature.type) {
            OEMFoldingFeature.TYPE_FOLD -> FOLD
            OEMFoldingFeature.TYPE_HINGE -> HINGE
            else -> return null
        }
        val state = when (oemFeature.state) {
            OEMFoldingFeature.STATE_FLAT -> FLAT
            OEMFoldingFeature.STATE_HALF_OPENED -> HALF_OPENED
            else -> return null
        }
        val bounds = Bounds(oemFeature.bounds)
        return if (validBounds(windowMetrics, bounds)) {
            HardwareFoldingFeature(Bounds(oemFeature.bounds), type, state)
        } else {
            null
        }
    }

    internal fun translate(
        @UiContext context: Context,
        info: OEMWindowLayoutInfo,
    ): WindowLayoutInfo {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            translate(computeCurrentWindowMetrics(context), info)
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (context is Activity)) {
            translate(computeCurrentWindowMetrics(context), info)
        } else {
            throw UnsupportedOperationException(
                "Display Features are only supported after Q. Display features for non-Activity " +
                    "contexts are not expected to be reported on devices running Q."
            )
        }
    }

    internal fun translate(
        windowMetrics: WindowMetrics,
        info: OEMWindowLayoutInfo
    ): WindowLayoutInfo {
        val features = info.displayFeatures.mapNotNull { feature ->
            when (feature) {
                is OEMFoldingFeature -> translate(windowMetrics, feature)
                else -> null
            }
        }
        return WindowLayoutInfo(features)
    }

    /**
     * Checks the bounds for a [FoldingFeature] within a given [WindowMetrics]. Validates the
     * following:
     *  - [Bounds] are not `0`
     *  - [Bounds] are either full width or full height
     *  - [Bounds] do not take up the entire [windowMetrics]
     * @param windowMetrics Extracted from a [UiContext] housing the [FoldingFeature].
     * @param bounds the bounds of a [FoldingFeature]
     * @return true if the bounds are valid for the [WindowMetrics], false otherwise.
     */
    private fun validBounds(windowMetrics: WindowMetrics, bounds: Bounds): Boolean {
        val windowBounds = windowMetrics.bounds
        if (bounds.isZero) {
            return false
        }
        if (bounds.width != windowBounds.width() && bounds.height != windowBounds.height()) {
            return false
        }
        if (bounds.width < windowBounds.width() && bounds.height < windowBounds.height()) {
            return false
        }
        if (bounds.width == windowBounds.width() && bounds.height == windowBounds.height()) {
            return false
        }

        return true
    }
}