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