Home | History | Annotate | Download | only in qs
      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 
     46     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 
     53     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 
     68     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 
     74     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          */
    121         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 }