1 /*
2  * Copyright (C) 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 package com.android.wallpaper.picker.preview.ui.view
17 
18 import android.content.Context
19 import android.graphics.Point
20 import android.util.AttributeSet
21 import android.widget.LinearLayout
22 import com.android.wallpaper.R
23 import com.android.wallpaper.config.BaseFlags
24 import com.android.wallpaper.model.wallpaper.DeviceDisplayType
25 
26 /**
27  * This LinearLayout view group implements the dual preview view for the small preview screen for
28  * foldable devices.
29  */
30 class DualDisplayAspectRatioLayout(context: Context, attrs: AttributeSet?) :
31     LinearLayout(context, attrs) {
32 
33     private var previewDisplaySizes: Map<DeviceDisplayType, Point>? = null
34 
35     /**
36      * This measures the desired size of the preview views for both of foldable device's displays.
37      * Each preview view respects the aspect ratio of the display it corresponds to while trying to
38      * have the maximum possible height.
39      */
onMeasurenull40     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
41         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
42 
43         if (previewDisplaySizes == null) {
44             setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
45             return
46         }
47 
48         // there are three spaces to consider
49         // the margin before the folded preview, the margin in between the folded and unfolded and
50         // the margin after the unfolded view
51         val totalMarginPixels =
52             context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt() * 3
53 
54         // TODO: This only works for portrait mode currently, need to incorporate landscape
55         val parentWidth = this.measuredWidth - totalMarginPixels
56 
57         val smallDisplaySize = checkNotNull(getPreviewDisplaySize(DeviceDisplayType.FOLDED))
58         val largeDisplaySize = checkNotNull(getPreviewDisplaySize(DeviceDisplayType.UNFOLDED))
59 
60         // calculate the aspect ratio (ar) of the folded display
61         val smallDisplayAR = smallDisplaySize.x.toFloat() / smallDisplaySize.y
62 
63         // calculate the aspect ratio of the unfolded display
64         val largeDisplayAR = largeDisplaySize.x.toFloat() / largeDisplaySize.y
65 
66         // Width based calculation
67         var newHeight = parentWidth / (largeDisplayAR + smallDisplayAR)
68         if (newHeight > this.measuredHeight && BaseFlags.get().isNewPickerUi()) {
69             // If new height derived from width is larger than original height, use height based
70             // calculation.
71             newHeight = this.measuredHeight.toFloat()
72         }
73 
74         val widthFolded = newHeight * smallDisplayAR
75         val widthUnfolded = newHeight * largeDisplayAR
76 
77         val foldedView = getChildAt(0)
78         foldedView.measure(
79             MeasureSpec.makeMeasureSpec(widthFolded.toInt(), MeasureSpec.EXACTLY),
80             MeasureSpec.makeMeasureSpec(newHeight.toInt(), MeasureSpec.EXACTLY),
81         )
82 
83         val unfoldedView = getChildAt(1)
84         unfoldedView.measure(
85             MeasureSpec.makeMeasureSpec(widthUnfolded.toInt(), MeasureSpec.EXACTLY),
86             MeasureSpec.makeMeasureSpec(newHeight.toInt(), MeasureSpec.EXACTLY),
87         )
88 
89         val marginPixels =
90             context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt()
91 
92         setMeasuredDimension(
93             MeasureSpec.makeMeasureSpec(
94                 (widthFolded + widthUnfolded + 2 * marginPixels).toInt(),
95                 MeasureSpec.EXACTLY,
96             ),
97             MeasureSpec.makeMeasureSpec(newHeight.toInt(), MeasureSpec.EXACTLY),
98         )
99     }
100 
onLayoutnull101     override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
102         // margins
103         val marginPixels =
104             context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt()
105 
106         // the handheld preview will be position first
107         val foldedView = getChildAt(0)
108         val foldedViewWidth = foldedView.measuredWidth
109         val foldedViewHeight = foldedView.measuredHeight
110         foldedView.layout(0 + marginPixels, 0, foldedViewWidth + marginPixels, foldedViewHeight)
111 
112         // the unfolded view will be position after
113         val unfoldedView = getChildAt(1)
114         val unfoldedViewWidth = unfoldedView.measuredWidth
115         val unfoldedViewHeight = unfoldedView.measuredHeight
116         unfoldedView.layout(
117             foldedViewWidth + 2 * marginPixels,
118             0,
119             unfoldedViewWidth + foldedViewWidth + 2 * marginPixels,
120             unfoldedViewHeight,
121         )
122     }
123 
setDisplaySizesnull124     fun setDisplaySizes(displaySizes: Map<DeviceDisplayType, Point>) {
125         previewDisplaySizes = displaySizes
126     }
127 
getPreviewDisplaySizenull128     fun getPreviewDisplaySize(display: DeviceDisplayType): Point? {
129         return previewDisplaySizes?.get(display)
130     }
131 
132     companion object {
133         /** Defines children view ids for [DualDisplayAspectRatioLayout]. */
DeviceDisplayTypenull134         fun DeviceDisplayType.getViewId(): Int {
135             return when (this) {
136                 DeviceDisplayType.SINGLE ->
137                     throw IllegalStateException(
138                         "DualDisplayAspectRatioLayout does not supper handheld DeviceDisplayType"
139                     )
140                 DeviceDisplayType.FOLDED -> R.id.small_preview_folded_preview
141                 DeviceDisplayType.UNFOLDED -> R.id.small_preview_unfolded_preview
142             }
143         }
144     }
145 }
146