• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }