diff options
| author | 2023-04-03 17:46:16 +0100 | |
|---|---|---|
| committer | 2023-05-03 10:29:58 +0000 | |
| commit | 32de1c9474dd22ff9bc43d13643bfeb7bbc20fee (patch) | |
| tree | 71a9e4ffa73268fee326a4f5cc6e261cdb904467 | |
| parent | 9e30a4e0f22fa689d634d96328eea2baaad2e7ea (diff) | |
Disabled SUBPIXEL_TEXT_FLAG for notification shade while fold unfold
Disabled the SUBPIXEL_TEXT_FLAG for all TextViews of the Notification Shade while folding and unfolding is in progress.
The SUBPIXEL_TEXT_FLAG is responsible for the default Android behaviour that tries to align the font to the nearest pixel, causing the text to jump as the nearest pixel keeps changing during folding and unfolding.
To solve this issue, we have disabled the SUBPIXEL_TEXT_FLAG for the TextViews of the Notification Shade during folding and unfolding, resulting in smoother text transitions.
Test: atest com.android.systemui.shared.animation.DisableSubpixelTextTransitionListenerTest.kt
Fixes: 227427854
Flag: SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
Change-Id: I6ada51f912ee1c086674b19ea39d4cab3ad161a8
| -rw-r--r-- | packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt | 84 | ||||
| -rw-r--r-- | packages/SystemUI/shared/src/com/android/systemui/utils/TraceUtils.kt (renamed from packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt) | 0 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/flags/Flags.kt | 5 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java | 12 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt | 6 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt | 6 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt | 130 |
7 files changed, 242 insertions, 1 deletions
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt new file mode 100644 index 000000000000..4c6d99a412b7 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListener.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 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.shared.animation + +import android.graphics.Paint +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.view.forEach +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.systemui.util.traceSection +import java.lang.ref.WeakReference + +/** + * A listener which disables subpixel flag for all TextView children of a given parent ViewGroup + * during fold/unfold transitions. + */ +class DisableSubpixelTextTransitionListener(private val rootView: ViewGroup?) : + TransitionProgressListener { + private val childrenTextViews: MutableList<WeakReference<TextView>> = mutableListOf() + private var isTransitionInProgress: Boolean = false + + override fun onTransitionStarted() { + isTransitionInProgress = true + traceSection("subpixelFlagSetForTextView") { + traceSection("subpixelFlagTraverseHierarchy") { + getAllChildTextView(rootView, childrenTextViews) + } + traceSection("subpixelFlagDisableForTextView") { + childrenTextViews.forEach { child -> + val childTextView = child.get() ?: return@forEach + childTextView.paintFlags = childTextView.paintFlags or Paint.SUBPIXEL_TEXT_FLAG + } + } + } + } + + override fun onTransitionFinished() { + if (!isTransitionInProgress) return + isTransitionInProgress = false + traceSection("subpixelFlagEnableForTextView") { + childrenTextViews.forEach { child -> + val childTextView = child.get() ?: return@forEach + childTextView.paintFlags = + childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG.inv() + } + childrenTextViews.clear() + } + } + + /** + * Populates a list of all TextView children of a given parent ViewGroup + * + * @param parent the root ViewGroup for which to retrieve TextView children + * @param childrenTextViews the list to store the retrieved TextView children + */ + private fun getAllChildTextView( + parent: ViewGroup?, + childrenTextViews: MutableList<WeakReference<TextView>> + ) { + parent?.forEach { child -> + when (child) { + is ViewGroup -> getAllChildTextView(child, childrenTextViews) + is TextView -> { + if ((child.paintFlags and Paint.SUBPIXEL_TEXT_FLAG) <= 0) { + childrenTextViews.add(WeakReference(child)) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/utils/TraceUtils.kt index 64234c205617..64234c205617 100644 --- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/utils/TraceUtils.kt diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index cd2bf13e37a2..af6567dad8a3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -689,6 +689,11 @@ object Flags { val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature", namespace = "vpn") + // TODO(b/277201412): Tracking Bug + @JvmField + val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = + unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true) + // TODO(b/278761837): Tracking Bug @JvmField val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter", diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 7cc257ba0a97..cb3fa15655b2 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -54,6 +54,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor; import com.android.systemui.multishade.ui.view.MultiShadeView; +import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationInsetsController; @@ -68,9 +69,11 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.window.StatusBarWindowStateController; +import com.android.systemui.unfold.UnfoldTransitionProgressProvider; import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; +import java.util.Optional; import java.util.function.Consumer; import javax.inject.Inject; @@ -114,7 +117,7 @@ public class NotificationShadeWindowViewController { private boolean mIsTrackingBarGesture = false; private boolean mIsOcclusionTransitionRunning = false; - + private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener; private final Consumer<TransitionStep> mLockscreenToDreamingTransition = (TransitionStep step) -> { mIsOcclusionTransitionRunning = @@ -139,6 +142,7 @@ public class NotificationShadeWindowViewController { LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, NotificationShadeWindowController controller, + Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, NotificationInsetsController notificationInsetsController, AmbientState ambientState, @@ -174,6 +178,7 @@ public class NotificationShadeWindowViewController { // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); + mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView); KeyguardBouncerViewBinder.bind( mView.findViewById(R.id.keyguard_bouncer_container), keyguardBouncerViewModel, @@ -184,6 +189,11 @@ public class NotificationShadeWindowViewController { mLockscreenToDreamingTransition); mClock = clock; + if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) { + unfoldTransitionProgressProvider.ifPresent( + progressProvider -> progressProvider.addCallback( + mDisableSubpixelTextTransitionListener)); + } if (ComposeFacade.INSTANCE.isComposeAvailable() && featureFlags.isEnabled(Flags.DUAL_SHADE)) { mMultiShadeMotionEventInteractor = multiShadeMotionEventInteractorProvider.get(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index bc8ab1faf9eb..16277de850d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -71,6 +72,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import java.util.Optional @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -101,6 +103,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController + @Mock + private lateinit var unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel @@ -129,6 +133,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.DUAL_SHADE, false) + featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) val inputProxy = MultiShadeInputProxy() testScope = TestScope() @@ -158,6 +163,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { lockIconViewController, centralSurfaces, notificationShadeWindowController, + unfoldTransitionProgressProvider, keyguardUnlockAnimationController, notificationInsetsController, ambientState, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 56385b2ec5da..16af208fd531 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -53,6 +53,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController +import com.android.systemui.unfold.UnfoldTransitionProgressProvider import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -71,6 +72,7 @@ import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import java.util.Optional @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidTestingRunner::class) @@ -103,6 +105,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController + @Mock + private lateinit var unfoldTransitionProgressProvider: Optional<UnfoldTransitionProgressProvider> @Mock private lateinit var notificationInsetsController: NotificationInsetsController @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock @@ -141,6 +145,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.DUAL_SHADE, false) + featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) val inputProxy = MultiShadeInputProxy() testScope = TestScope() val multiShadeInteractor = @@ -169,6 +174,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { lockIconViewController, centralSurfaces, notificationShadeWindowController, + unfoldTransitionProgressProvider, keyguardUnlockAnimationController, notificationInsetsController, ambientState, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt new file mode 100644 index 000000000000..fc230e362735 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/DisableSubpixelTextTransitionListenerTest.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 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.shared.animation + +import android.graphics.Paint +import android.testing.AndroidTestingRunner +import android.widget.FrameLayout +import android.widget.TextView +import androidx.test.filters.SmallTest +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 + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DisableSubpixelTextTransitionListenerTest : SysuiTestCase() { + + private lateinit var disableSubpixelTextTransitionListener: + DisableSubpixelTextTransitionListener + private var rootViewWithNoTextView = FrameLayout(context) + private var rootView = FrameLayout(context) + private var childView = FrameLayout(context) + private var childViewWithNoTextView = FrameLayout(context) + private var childTextView = TextView(context) + private var childOfChildTextView = TextView(context) + + @Before + fun setup() { + + childView.addView(childOfChildTextView) + rootView.addView(childTextView) + rootView.addView(childView) + } + + @Test + fun onTransitionStarted_addsSubpixelFlagToChildTextView() { + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + + assertThat(childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isGreaterThan(0) + } + + @Test + fun onTransitionStarted_addsSupbixelFlagToChildOfChildTextView() { + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + + assertThat(childOfChildTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isGreaterThan(0) + } + + @Test + fun onTransitionFinished_removeSupbixelFlagFromChildTextView() { + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + disableSubpixelTextTransitionListener.onTransitionFinished() + + assertThat(childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isEqualTo(0) + } + + @Test + fun onTransitionFinished_removeSupbixelFlagFromChildOfChildTextView() { + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + disableSubpixelTextTransitionListener.onTransitionFinished() + + assertThat(childOfChildTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isEqualTo(0) + } + + @Test + fun whenRootViewIsNull_runWithoutExceptions() { + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(null) + + disableSubpixelTextTransitionListener.onTransitionStarted() + disableSubpixelTextTransitionListener.onTransitionFinished() + } + + @Test + fun whenFlagAlreadyPresent_flagNotRemovedOnTransitionFinished() { + childTextView.paintFlags = childTextView.paintFlags or Paint.SUBPIXEL_TEXT_FLAG + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + disableSubpixelTextTransitionListener.onTransitionFinished() + + assertThat(childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isGreaterThan(0) + } + + @Test + fun whenFlagNotPresent_flagRemovedOnTransitionFinished() { + childTextView.paintFlags = childTextView.paintFlags or Paint.SUBPIXEL_TEXT_FLAG + disableSubpixelTextTransitionListener = DisableSubpixelTextTransitionListener(rootView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + disableSubpixelTextTransitionListener.onTransitionFinished() + + assertThat(childOfChildTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isEqualTo(0) + } + + @Test + fun whenRootViewHasNoChildTextView_flagNotAddedToRelatedTextviews() { + rootViewWithNoTextView.addView(childViewWithNoTextView) + rootView.addView(rootViewWithNoTextView) + disableSubpixelTextTransitionListener = + DisableSubpixelTextTransitionListener(rootViewWithNoTextView) + + disableSubpixelTextTransitionListener.onTransitionStarted() + + assertThat(childTextView.paintFlags and Paint.SUBPIXEL_TEXT_FLAG).isEqualTo(0) + } +} |