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