1 /* 2 * Copyright (C) 2019 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 com.android.systemui.qs 18 19 import android.content.Context 20 import android.util.AttributeSet 21 import android.view.View 22 import android.widget.FrameLayout 23 import com.android.systemui.R 24 25 /** 26 * Container for the Next Alarm and Ringer status texts in [QuickStatusBarHeader]. 27 * 28 * If both elements are visible, it splits the available space according to the following rules: 29 * * If both views add up to less than the total space, they take all the space they need. 30 * * If both views are larger than half the space, each view takes half the space. 31 * * Otherwise, the smaller view takes the space it needs and the larger one takes all remaining 32 * space. 33 */ 34 class QSHeaderInfoLayout @JvmOverloads constructor( 35 context: Context, 36 attrs: AttributeSet? = null, 37 defStyle: Int = 0, 38 defStyleRes: Int = 0 39 ) : FrameLayout(context, attrs, defStyle, defStyleRes) { 40 41 private lateinit var alarmContainer: View 42 private lateinit var ringerContainer: View 43 private lateinit var statusSeparator: View 44 private val location = Location(0, 0) 45 onFinishInflatenull46 override fun onFinishInflate() { 47 super.onFinishInflate() 48 alarmContainer = findViewById(R.id.alarm_container) 49 ringerContainer = findViewById(R.id.ringer_container) 50 statusSeparator = findViewById(R.id.status_separator) 51 } 52 onLayoutnull53 override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 54 // At most one view is there 55 if (statusSeparator.visibility == View.GONE) super.onLayout(changed, l, t, r, b) 56 else { 57 val layoutRTL = isLayoutRtl 58 val width = r - l 59 val height = b - t 60 var offset = 0 61 62 offset += alarmContainer.layoutView(width, height, offset, layoutRTL) 63 offset += statusSeparator.layoutView(width, height, offset, layoutRTL) 64 ringerContainer.layoutView(width, height, offset, layoutRTL) 65 } 66 } 67 layoutViewnull68 private fun View.layoutView(pWidth: Int, pHeight: Int, offset: Int, RTL: Boolean): Int { 69 location.setLocationFromOffset(pWidth, offset, this.measuredWidth, RTL) 70 layout(location.left, 0, location.right, pHeight) 71 return this.measuredWidth 72 } 73 onMeasurenull74 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 75 super.onMeasure( 76 MeasureSpec.makeMeasureSpec( 77 MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST), 78 heightMeasureSpec) 79 val width = MeasureSpec.getSize(widthMeasureSpec) 80 // Once we measure the views, using as much space as they need, we need to remeasure them 81 // assigning them their final width. This is because TextViews decide whether to MARQUEE 82 // after onMeasure. 83 if (statusSeparator.visibility != View.GONE) { 84 val alarmWidth = alarmContainer.measuredWidth 85 val separatorWidth = statusSeparator.measuredWidth 86 val ringerWidth = ringerContainer.measuredWidth 87 val availableSpace = MeasureSpec.getSize(width) - separatorWidth 88 if (alarmWidth < availableSpace / 2) { 89 measureChild( 90 ringerContainer, 91 MeasureSpec.makeMeasureSpec( 92 Math.min(ringerWidth, availableSpace - alarmWidth), 93 MeasureSpec.AT_MOST), 94 heightMeasureSpec) 95 } else if (ringerWidth < availableSpace / 2) { 96 measureChild(alarmContainer, 97 MeasureSpec.makeMeasureSpec( 98 Math.min(alarmWidth, availableSpace - ringerWidth), 99 MeasureSpec.AT_MOST), 100 heightMeasureSpec) 101 } else { 102 measureChild( 103 alarmContainer, 104 MeasureSpec.makeMeasureSpec(availableSpace / 2, MeasureSpec.AT_MOST), 105 heightMeasureSpec) 106 measureChild( 107 ringerContainer, 108 MeasureSpec.makeMeasureSpec(availableSpace / 2, MeasureSpec.AT_MOST), 109 heightMeasureSpec) 110 } 111 } 112 setMeasuredDimension(width, measuredHeight) 113 } 114 115 private data class Location(var left: Int, var right: Int) { 116 /** 117 * Sets the [left] and [right] with the correct values for laying out the child, respecting 118 * RTL. Only set the variable through here to prevent concurrency issues. 119 * This is done to prevent allocation of [Pair] in [onLayout]. 120 */ setLocationFromOffsetnull121 fun setLocationFromOffset(parentWidth: Int, offset: Int, width: Int, RTL: Boolean) { 122 if (RTL) { 123 left = parentWidth - offset - width 124 right = parentWidth - offset 125 } else { 126 left = offset 127 right = offset + width 128 } 129 } 130 } 131 }