1 /*
2  * Copyright (C) 2021 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.statusbar.chips.ui.view
18 
19 import android.content.Context
20 import android.util.AttributeSet
21 import android.widget.Chronometer
22 import androidx.annotation.UiThread
23 
24 /**
25  * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an
26  * activity.
27  *
28  * This class handles:
29  * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change
30  *    slightly each second because the width of each number is slightly different.
31  *
32  *    Instead, we save the largest number width seen so far and ensure that the chip is at least
33  *    that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to
34  *    1:00:00), but never smaller.
35  * 2) Hiding the text if the time gets too long for the space available. Once the text has been
36  *    hidden, it remains hidden for the duration of the activity.
37  *
38  * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
39  * text will also be hidden in landscape (even if there is enough space for it in landscape).
40  */
41 class ChipChronometer
42 @JvmOverloads
43 constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) :
44     Chronometer(context, attrs, defStyle) {
45 
46     // Minimum width that the text view can be. Corresponds with the largest number width seen so
47     // far.
48     private var minimumTextWidth: Int = 0
49 
50     // True if the text is too long for the space available, so the text should be hidden.
51     private var shouldHideText: Boolean = false
52 
setBasenull53     override fun setBase(base: Long) {
54         // These variables may have changed during the previous activity, so re-set them before the
55         // new activity starts.
56         minimumTextWidth = 0
57         shouldHideText = false
58         visibility = VISIBLE
59         super.setBase(base)
60     }
61 
62     /** Sets whether this view should hide its text or not. */
63     @UiThread
setShouldHideTextnull64     fun setShouldHideText(shouldHideText: Boolean) {
65         this.shouldHideText = shouldHideText
66         requestLayout()
67     }
68 
onMeasurenull69     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
70         if (shouldHideText) {
71             setMeasuredDimension(0, 0)
72             return
73         }
74 
75         // Evaluate how wide the text *wants* to be if it had unlimited space.
76         super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
77         val desiredTextWidth = measuredWidth
78 
79         // Evaluate how wide the text *can* be based on the enforced constraints
80         val enforcedTextWidth = resolveSize(desiredTextWidth, widthMeasureSpec)
81 
82         if (desiredTextWidth > enforcedTextWidth) {
83             shouldHideText = true
84             // Changing visibility ensures that the content description is not read aloud when the
85             // time isn't displayed.
86             visibility = GONE
87             setMeasuredDimension(0, 0)
88         } else {
89             // It's possible that the current text could fit in a smaller width, but we don't want
90             // the chip to change size every second. Instead, keep it at the minimum required width.
91             minimumTextWidth = desiredTextWidth.coerceAtLeast(minimumTextWidth)
92             setMeasuredDimension(minimumTextWidth, MeasureSpec.getSize(heightMeasureSpec))
93         }
94     }
95 }
96