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