From 649b504185826c2f88c1d70ac06544adb586ba2c Mon Sep 17 00:00:00 2001 From: Evan Laird Date: Fri, 13 May 2022 16:56:24 -0400 Subject: [PATCH] Change the status bar clock's measuring scheme This change implements a measuring scheme such that the clock will only get wider to accomodate wider numbers, but will never shrink until either: - The number of characters changes (e.g. going from 12:59 -> 1:00) - The density or font scale changes Test: atest ClockTest Test: manual Fixes: 163909570 Change-Id: I8382a479f5cf1afb594d1e6238868f76c1220da9 --- .../systemui/statusbar/policy/Clock.java | 30 ++++ .../systemui/statusbar/policy/ClockTest.kt | 130 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 4c43734836c4a..576962dee7475 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -103,6 +103,10 @@ public class Clock extends TextView implements private boolean mShowSeconds; private Handler mSecondsHandler; + // Fields to cache the width so the clock remains at an approximately constant width + private int mCharsAtCurrentWidth = -1; + private int mCachedWidth = -1; + /** * Color to be set on this {@link TextView}, when wallpaperTextColor is not utilized. */ @@ -302,6 +306,32 @@ public class Clock extends TextView implements setContentDescription(mContentDescriptionFormat.format(mCalendar.getTime())); } + /** + * In order to avoid the clock growing and shrinking due to proportional fonts, we want to + * cache the drawn width at a given number of characters (removing the cache when it changes), + * and only use the biggest value. This means that the clock width with grow to the maximum + * size over time, but reset whenever the number of characters changes (or the configuration + * changes) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int chars = getText().length(); + if (chars != mCharsAtCurrentWidth) { + mCharsAtCurrentWidth = chars; + mCachedWidth = getMeasuredWidth(); + return; + } + + int measuredWidth = getMeasuredWidth(); + if (mCachedWidth > measuredWidth) { + setMeasuredDimension(mCachedWidth, getMeasuredHeight()); + } else { + mCachedWidth = measuredWidth; + } + } + @Override public void onTuningChanged(String key, String newValue) { if (CLOCK_SECONDS.equals(key)) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt new file mode 100644 index 0000000000000..22c72cc495b14 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 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.policy + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View.MeasureSpec.UNSPECIFIED +import android.view.View.MeasureSpec.makeMeasureSpec +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.LinearLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.runner.RunWith + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ClockTest : SysuiTestCase() { + private lateinit var clockView: Clock + + @Before + fun setUp() { + allowTestableLooperAsMainThread() + TestableLooper.get(this).runWithLooper { + val container = LinearLayout(context) + val lp = LinearLayout.LayoutParams(1000, WRAP_CONTENT) + container.layoutParams = lp + clockView = Clock(context, null) + container.addView(clockView) + measureClock() + } + } + + @Test + fun testWidthDoesNotDecrease_sameCharLength() { + // GIVEN time is narrow + clockView.text = ONE_3 + measureClock() + val width1 = clockView.measuredWidth + + // WHEN the text changes to be wider characters + clockView.text = ZERO_3 + measureClock() + val width2 = clockView.measuredWidth + + // THEN the width should be wider (or equals when using monospace font) + assertThat(width2).isAtLeast(width1) + } + + @Test + fun testWidthDoesNotDecrease_narrowerFont_sameNumberOfChars() { + // GIVEN time is wide + clockView.text = ZERO_3 + measureClock() + val width1 = clockView.measuredWidth + + // WHEN the text changes to a narrower font + clockView.text = ONE_3 + measureClock() + val width2 = clockView.measuredWidth + + // THEN the width should not have decreased, and they should in fact be the same + assertThat(width2).isEqualTo(width1) + } + + @Test + fun testWidthIncreases_whenCharsChanges() { + // GIVEN wide 3-char text + clockView.text = ZERO_3 + measureClock() + val width1 = clockView.measuredWidth + + // WHEN text changes to 4-char wide text + clockView.text = ZERO_4 + measureClock() + val width2 = clockView.measuredWidth + + // THEN the text field is wider + assertThat(width2).isGreaterThan(width1) + } + + @Test + fun testWidthDecreases_whenCharsChange_longToShort() { + // GIVEN wide 4-char text + clockView.text = ZERO_4 + measureClock() + val width1 = clockView.measuredWidth + + // WHEN number of characters changes to a narrow 3-char text + clockView.text = ONE_3 + measureClock() + val width2 = clockView.measuredWidth + + // THEN the width can shrink, because number of chars changed + assertThat(width2).isLessThan(width1) + } + + private fun measureClock() { + clockView.measure( + makeMeasureSpec(0, UNSPECIFIED), + makeMeasureSpec(0, UNSPECIFIED) + ) + } +} + +/** + * In a non-monospace font, it is expected that "0:00" is wider than "1:11" + */ +private const val ZERO_3 = "0:00" +private const val ZERO_4 = "00:00" +private const val ONE_3 = "1:11" +private const val ONE_4 = "11:11"