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.model.wallpaper.DeviceDisplayType 24 import kotlin.math.max 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( 31 context: Context, 32 attrs: AttributeSet?, 33 ) : LinearLayout(context, attrs) { 34 35 private var previewDisplaySizes: Map<DeviceDisplayType, Point>? = null 36 37 /** 38 * This measures the desired size of the preview views for both of foldable device's displays. 39 * Each preview view respects the aspect ratio of the display it corresponds to while trying to 40 * have the maximum possible height. 41 */ onMeasurenull42 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 43 super.onMeasure(widthMeasureSpec, heightMeasureSpec) 44 45 if (previewDisplaySizes == null) { 46 setMeasuredDimension(widthMeasureSpec, heightMeasureSpec) 47 return 48 } 49 50 // there are three spaces to consider 51 // the margin before the folded preview, the margin in between the folded and unfolded and 52 // the margin after the unfolded view 53 val totalMarginPixels = 54 context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt() * 3 55 56 // TODO: This only works for portrait mode currently, need to incorporate landscape 57 val parentWidth = this.measuredWidth - totalMarginPixels 58 59 val smallDisplaySize = checkNotNull(getPreviewDisplaySize(DeviceDisplayType.FOLDED)) 60 val largeDisplaySize = checkNotNull(getPreviewDisplaySize(DeviceDisplayType.UNFOLDED)) 61 62 // calculate the aspect ratio (ar) of the folded display 63 val smallDisplayAR = smallDisplaySize.x.toFloat() / smallDisplaySize.y 64 65 // calculate the aspect ratio of the unfolded display 66 val largeDisplayAR = largeDisplaySize.x.toFloat() / largeDisplaySize.y 67 68 val sizeMultiplier = parentWidth / (largeDisplayAR + smallDisplayAR) 69 val widthFolded = (sizeMultiplier * smallDisplayAR).toInt() 70 val heightFolded = (widthFolded / smallDisplayAR).toInt() 71 72 val widthUnfolded = (sizeMultiplier * largeDisplayAR).toInt() 73 val heightUnfolded = (widthUnfolded / largeDisplayAR).toInt() 74 75 val foldedView = getChildAt(0) 76 foldedView.measure( 77 MeasureSpec.makeMeasureSpec( 78 widthFolded, 79 MeasureSpec.EXACTLY, 80 ), 81 MeasureSpec.makeMeasureSpec( 82 heightFolded, 83 MeasureSpec.EXACTLY, 84 ), 85 ) 86 87 val unfoldedView = getChildAt(1) 88 unfoldedView.measure( 89 MeasureSpec.makeMeasureSpec( 90 widthUnfolded, 91 MeasureSpec.EXACTLY, 92 ), 93 MeasureSpec.makeMeasureSpec( 94 heightUnfolded, 95 MeasureSpec.EXACTLY, 96 ), 97 ) 98 99 val marginPixels = 100 context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt() 101 102 setMeasuredDimension( 103 MeasureSpec.makeMeasureSpec( 104 widthFolded + widthUnfolded + 2 * marginPixels, 105 MeasureSpec.EXACTLY, 106 ), 107 MeasureSpec.makeMeasureSpec( 108 max(heightFolded, heightUnfolded), 109 MeasureSpec.EXACTLY, 110 ) 111 ) 112 } 113 onLayoutnull114 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 115 // margins 116 val marginPixels = 117 context.resources.getDimension(R.dimen.small_preview_inter_preview_margin).toInt() 118 119 // the handheld preview will be position first 120 val foldedView = getChildAt(0) 121 val foldedViewWidth = foldedView.measuredWidth 122 val foldedViewHeight = foldedView.measuredHeight 123 foldedView.layout(0 + marginPixels, 0, foldedViewWidth + marginPixels, foldedViewHeight) 124 125 // the unfolded view will be position after 126 val unfoldedView = getChildAt(1) 127 val unfoldedViewWidth = unfoldedView.measuredWidth 128 val unfoldedViewHeight = unfoldedView.measuredHeight 129 unfoldedView.layout( 130 foldedViewWidth + 2 * marginPixels, 131 0, 132 unfoldedViewWidth + foldedViewWidth + 2 * marginPixels, 133 unfoldedViewHeight 134 ) 135 } 136 setDisplaySizesnull137 fun setDisplaySizes(displaySizes: Map<DeviceDisplayType, Point>) { 138 previewDisplaySizes = displaySizes 139 } 140 getPreviewDisplaySizenull141 fun getPreviewDisplaySize(display: DeviceDisplayType): Point? { 142 return previewDisplaySizes?.get(display) 143 } 144 145 companion object { 146 /** Defines children view ids for [DualDisplayAspectRatioLayout]. */ DeviceDisplayTypenull147 fun DeviceDisplayType.getViewId(): Int { 148 return when (this) { 149 DeviceDisplayType.SINGLE -> 150 throw IllegalStateException( 151 "DualDisplayAspectRatioLayout does not supper handheld DeviceDisplayType" 152 ) 153 DeviceDisplayType.FOLDED -> R.id.small_preview_folded_preview 154 DeviceDisplayType.UNFOLDED -> R.id.small_preview_unfolded_preview 155 } 156 } 157 } 158 } 159