Merge "[Ongoing Call Chip] UI tweaks: Don't have the chip width change each second, and hide the time if it's too large for the space available." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
081ec33862
@@ -29,18 +29,17 @@
|
|||||||
android:src="@*android:drawable/ic_phone"
|
android:src="@*android:drawable/ic_phone"
|
||||||
android:layout_width="@dimen/ongoing_call_chip_icon_size"
|
android:layout_width="@dimen/ongoing_call_chip_icon_size"
|
||||||
android:layout_height="@dimen/ongoing_call_chip_icon_size"
|
android:layout_height="@dimen/ongoing_call_chip_icon_size"
|
||||||
android:paddingEnd="@dimen/ongoing_call_chip_icon_text_padding"
|
|
||||||
android:tint="?android:attr/colorPrimary"
|
android:tint="?android:attr/colorPrimary"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- TODO(b/183229367): The text in this view isn't quite centered within the chip. -->
|
<!-- TODO(b/183229367): The text in this view isn't quite centered within the chip. -->
|
||||||
<!-- TODO(b/183229367): This text view's width shouldn't change as the time increases. -->
|
<com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer
|
||||||
<Chronometer
|
|
||||||
android:id="@+id/ongoing_call_chip_time"
|
android:id="@+id/ongoing_call_chip_time"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:gravity="center"
|
android:gravity="center|start"
|
||||||
|
android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding"
|
||||||
android:textAppearance="@android:style/TextAppearance.Material.Small"
|
android:textAppearance="@android:style/TextAppearance.Material.Small"
|
||||||
android:fontFamily="@*android:string/config_headlineFontFamily"
|
android:fontFamily="@*android:string/config_headlineFontFamily"
|
||||||
android:textColor="?android:attr/colorPrimary"
|
android:textColor="?android:attr/colorPrimary"
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.statusbar.phone.ongoingcall
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
import android.widget.Chronometer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Chronometer] specifically for the ongoing call chip in the status bar.
|
||||||
|
*
|
||||||
|
* This class handles:
|
||||||
|
* 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would
|
||||||
|
* change slightly each second because the width of each number is slightly different.
|
||||||
|
*
|
||||||
|
* Instead, we save the largest number width seen so far and ensure that the chip is at least
|
||||||
|
* that wide. This means the chip may get larger over time (e.g. in the transition from 59:59
|
||||||
|
* to 1:00:00), but never smaller.
|
||||||
|
*
|
||||||
|
* 2) Hiding the text if the time gets too long for the space available. Once the text has been
|
||||||
|
* hidden, it remains hidden for the duration of the call.
|
||||||
|
*
|
||||||
|
* Note that if the text was too big in portrait mode, resulting in the text being hidden, then the
|
||||||
|
* text will also be hidden in landscape (even if there is enough space for it in landscape).
|
||||||
|
*/
|
||||||
|
class OngoingCallChronometer @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyle: Int = 0
|
||||||
|
) : Chronometer(context, attrs, defStyle) {
|
||||||
|
|
||||||
|
// Minimum width that the text view can be. Corresponds with the largest number width seen so
|
||||||
|
// far.
|
||||||
|
var minimumTextWidth: Int = 0
|
||||||
|
|
||||||
|
// True if the text is too long for the space available, so the text should be hidden.
|
||||||
|
var shouldHideText: Boolean = false
|
||||||
|
|
||||||
|
override fun setBase(base: Long) {
|
||||||
|
// These variables may have changed during the previous call, so re-set them before the new
|
||||||
|
// call starts.
|
||||||
|
minimumTextWidth = 0
|
||||||
|
shouldHideText = false
|
||||||
|
super.setBase(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
if (shouldHideText) {
|
||||||
|
setMeasuredDimension(0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate how wide the text *wants* to be if it had unlimited space.
|
||||||
|
super.onMeasure(
|
||||||
|
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
||||||
|
heightMeasureSpec)
|
||||||
|
val desiredTextWidth = measuredWidth
|
||||||
|
|
||||||
|
// Evaluate how wide the text *can* be based on the enforced constraints
|
||||||
|
val enforcedTextWidth = resolveSize(desiredTextWidth, widthMeasureSpec)
|
||||||
|
|
||||||
|
if (desiredTextWidth > enforcedTextWidth) {
|
||||||
|
shouldHideText = true
|
||||||
|
setMeasuredDimension(0, 0)
|
||||||
|
} else {
|
||||||
|
// It's possible that the current text could fit in a smaller width, but we don't want
|
||||||
|
// the chip to change size every second. Instead, keep it at the minimum required width.
|
||||||
|
minimumTextWidth = desiredTextWidth.coerceAtLeast(minimumTextWidth)
|
||||||
|
setMeasuredDimension(minimumTextWidth, MeasureSpec.getSize(heightMeasureSpec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.statusbar.phone.ongoingcall
|
||||||
|
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.testing.TestableLooper
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
private const val TEXT_VIEW_MAX_WIDTH = 400
|
||||||
|
|
||||||
|
// When a [Chronometer] is created, it starts off with "00:00" as its text.
|
||||||
|
private const val INITIAL_TEXT = "00:00"
|
||||||
|
private const val LARGE_TEXT = "00:000"
|
||||||
|
private const val XL_TEXT = "00:0000"
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@TestableLooper.RunWithLooper
|
||||||
|
class OngoingCallChronometerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private lateinit var textView: OngoingCallChronometer
|
||||||
|
private lateinit var doesNotFitText: String
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
allowTestableLooperAsMainThread()
|
||||||
|
TestableLooper.get(this).runWithLooper {
|
||||||
|
val chipView = LayoutInflater.from(mContext)
|
||||||
|
.inflate(R.layout.ongoing_call_chip, null) as LinearLayout
|
||||||
|
textView = chipView.findViewById(R.id.ongoing_call_chip_time)!!
|
||||||
|
measureTextView()
|
||||||
|
calculateDoesNotFixText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun verifyTextSizes() {
|
||||||
|
val initialTextLength = textView.paint.measureText(INITIAL_TEXT)
|
||||||
|
val largeTextLength = textView.paint.measureText(LARGE_TEXT)
|
||||||
|
val xlTextLength = textView.paint.measureText(XL_TEXT)
|
||||||
|
|
||||||
|
// Assert that our test text sizes do what we expect them to do in the rest of the tests.
|
||||||
|
assertThat(initialTextLength).isLessThan(TEXT_VIEW_MAX_WIDTH)
|
||||||
|
assertThat(largeTextLength).isLessThan(TEXT_VIEW_MAX_WIDTH)
|
||||||
|
assertThat(xlTextLength).isLessThan(TEXT_VIEW_MAX_WIDTH)
|
||||||
|
assertThat(textView.paint.measureText(doesNotFitText)).isGreaterThan(TEXT_VIEW_MAX_WIDTH)
|
||||||
|
|
||||||
|
assertThat(largeTextLength).isGreaterThan(initialTextLength)
|
||||||
|
assertThat(xlTextLength).isGreaterThan(largeTextLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onMeasure_initialTextFitsInSpace_textDisplayed() {
|
||||||
|
assertThat(textView.measuredWidth).isGreaterThan(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onMeasure_newTextLargerThanPreviousText_widthGetsLarger() {
|
||||||
|
val initialTextLength = textView.measuredWidth
|
||||||
|
|
||||||
|
setTextAndMeasure(LARGE_TEXT)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isGreaterThan(initialTextLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onMeasure_newTextSmallerThanPreviousText_widthDoesNotGetSmaller() {
|
||||||
|
setTextAndMeasure(XL_TEXT)
|
||||||
|
val xlWidth = textView.measuredWidth
|
||||||
|
|
||||||
|
setTextAndMeasure(LARGE_TEXT)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isEqualTo(xlWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onMeasure_textDoesNotFit_textHidden() {
|
||||||
|
setTextAndMeasure(doesNotFitText)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isEqualTo(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onMeasure_newTextFitsButPreviousTextDidNot_textHidden() {
|
||||||
|
setTextAndMeasure(doesNotFitText)
|
||||||
|
|
||||||
|
setTextAndMeasure(LARGE_TEXT)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isEqualTo(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun resetBase_hadLongerTextThenSetBaseThenShorterText_widthIsShort() {
|
||||||
|
setTextAndMeasure(XL_TEXT)
|
||||||
|
val xlWidth = textView.measuredWidth
|
||||||
|
|
||||||
|
textView.base = 0L
|
||||||
|
setTextAndMeasure(INITIAL_TEXT)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isLessThan(xlWidth)
|
||||||
|
assertThat(textView.measuredWidth).isGreaterThan(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun setBase_wasHidingTextThenSetBaseThenShorterText_textShown() {
|
||||||
|
setTextAndMeasure(doesNotFitText)
|
||||||
|
|
||||||
|
textView.base = 0L
|
||||||
|
setTextAndMeasure(INITIAL_TEXT)
|
||||||
|
|
||||||
|
assertThat(textView.measuredWidth).isGreaterThan(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTextAndMeasure(text: String) {
|
||||||
|
textView.text = text
|
||||||
|
measureTextView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun measureTextView() {
|
||||||
|
textView.measure(
|
||||||
|
View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST),
|
||||||
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates what [doesNotFitText] should be. Needs to be done dynamically because different
|
||||||
|
* devices have different densities, which means the textView can fit different amounts of
|
||||||
|
* characters.
|
||||||
|
*/
|
||||||
|
private fun calculateDoesNotFixText() {
|
||||||
|
var currentText = XL_TEXT + "0"
|
||||||
|
while (textView.paint.measureText(currentText) <= TEXT_VIEW_MAX_WIDTH) {
|
||||||
|
currentText += "0"
|
||||||
|
}
|
||||||
|
doesNotFitText = currentText
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user