1 /*
<lambda>null2  * Copyright 2021 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.window.layout.adapter.extensions
18 
19 import android.app.Activity
20 import android.content.Context
21 import android.os.Build
22 import androidx.annotation.UiContext
23 import androidx.window.core.Bounds
24 import androidx.window.extensions.layout.DisplayFoldFeature
25 import androidx.window.extensions.layout.FoldingFeature as OEMFoldingFeature
26 import androidx.window.extensions.layout.SupportedWindowFeatures
27 import androidx.window.extensions.layout.WindowLayoutInfo as OEMWindowLayoutInfo
28 import androidx.window.layout.FoldingFeature
29 import androidx.window.layout.FoldingFeature.State.Companion.FLAT
30 import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
31 import androidx.window.layout.HardwareFoldingFeature
32 import androidx.window.layout.HardwareFoldingFeature.Type.Companion.FOLD
33 import androidx.window.layout.HardwareFoldingFeature.Type.Companion.HINGE
34 import androidx.window.layout.SupportedPosture
35 import androidx.window.layout.WindowLayoutInfo
36 import androidx.window.layout.WindowMetrics
37 import androidx.window.layout.WindowMetricsCalculatorCompat
38 
39 internal object ExtensionsWindowLayoutInfoAdapter {
40 
41     internal fun translate(
42         windowMetrics: WindowMetrics,
43         oemFeature: OEMFoldingFeature,
44     ): FoldingFeature? {
45         val type =
46             when (oemFeature.type) {
47                 OEMFoldingFeature.TYPE_FOLD -> FOLD
48                 OEMFoldingFeature.TYPE_HINGE -> HINGE
49                 else -> return null
50             }
51         val state =
52             when (oemFeature.state) {
53                 OEMFoldingFeature.STATE_FLAT -> FLAT
54                 OEMFoldingFeature.STATE_HALF_OPENED -> HALF_OPENED
55                 else -> return null
56             }
57         val bounds = Bounds(oemFeature.bounds)
58         return if (validBounds(windowMetrics, bounds)) {
59             HardwareFoldingFeature(Bounds(oemFeature.bounds), type, state)
60         } else {
61             null
62         }
63     }
64 
65     internal fun translate(
66         @UiContext context: Context,
67         info: OEMWindowLayoutInfo,
68     ): WindowLayoutInfo {
69         val calculator = WindowMetricsCalculatorCompat()
70         return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
71             translate(calculator.computeCurrentWindowMetrics(context), info)
72         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && (context is Activity)) {
73             translate(calculator.computeCurrentWindowMetrics(context), info)
74         } else {
75             throw UnsupportedOperationException(
76                 "Display Features are only supported after Q. Display features for non-Activity " +
77                     "contexts are not expected to be reported on devices running Q."
78             )
79         }
80     }
81 
82     internal fun translate(
83         windowMetrics: WindowMetrics,
84         info: OEMWindowLayoutInfo
85     ): WindowLayoutInfo {
86         val features =
87             info.displayFeatures.mapNotNull { feature ->
88                 when (feature) {
89                     is OEMFoldingFeature -> translate(windowMetrics, feature)
90                     else -> null
91                 }
92             }
93         return WindowLayoutInfo(features)
94     }
95 
96     internal fun translate(features: SupportedWindowFeatures): List<SupportedPosture> {
97         val isTableTopSupported =
98             features.displayFoldFeatures.any { feature ->
99                 feature.hasProperties(DisplayFoldFeature.FOLD_PROPERTY_SUPPORTS_HALF_OPENED)
100             }
101         return if (isTableTopSupported) {
102             listOf(SupportedPosture.TABLETOP)
103         } else {
104             emptyList()
105         }
106     }
107 
108     /**
109      * Checks the bounds for a [FoldingFeature] within a given [WindowMetrics]. Validates the
110      * following:
111      * - [Bounds] are not `0`
112      * - [Bounds] are either full width or full height
113      * - [Bounds] do not take up the entire [windowMetrics]
114      *
115      * @param windowMetrics Extracted from a [UiContext] housing the [FoldingFeature].
116      * @param bounds the bounds of a [FoldingFeature]
117      * @return true if the bounds are valid for the [WindowMetrics], false otherwise.
118      */
119     private fun validBounds(windowMetrics: WindowMetrics, bounds: Bounds): Boolean {
120         val windowBounds = windowMetrics.bounds
121         if (bounds.isZero) {
122             return false
123         }
124         if (bounds.width != windowBounds.width() && bounds.height != windowBounds.height()) {
125             return false
126         }
127         if (bounds.width < windowBounds.width() && bounds.height < windowBounds.height()) {
128             return false
129         }
130         if (bounds.width == windowBounds.width() && bounds.height == windowBounds.height()) {
131             return false
132         }
133 
134         return true
135     }
136 }
137