diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 584d27f84ceb1..d806afa91dd1f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -119,6 +119,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final QSLogger mLogger; private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; + private final FooterActionsViewBinder mFooterActionsViewBinder; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; private boolean mShowCollapsedOnKeyguard; private boolean mLastKeyguardAndExpanded; @@ -176,6 +177,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory, + FooterActionsViewBinder footerActionsViewBinder, LargeScreenShadeInterpolator largeScreenShadeInterpolator, FeatureFlags featureFlags) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; @@ -192,6 +194,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mDumpManager = dumpManager; mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; + mFooterActionsViewBinder = footerActionsViewBinder; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); } @@ -284,7 +287,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca if (!ComposeFacade.INSTANCE.isComposeAvailable()) { Log.d(TAG, "Binding the View implementation of the QS footer actions"); - FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, + mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner); return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 19215867e6789..9c9ad33e4918f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -33,27 +33,27 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.binder.IconViewBinder +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.people.ui.view.PeopleViewBinder.bind import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel +import javax.inject.Inject import kotlin.math.roundToInt -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch /** A ViewBinder for [FooterActionsViewBinder]. */ -object FooterActionsViewBinder { +@SysUISingleton +class FooterActionsViewBinder @Inject constructor() { /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */ - @JvmStatic fun create(context: Context): LinearLayout { return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null) as LinearLayout } /** Bind [view] to [viewModel]. */ - @JvmStatic fun bind( view: LinearLayout, viewModel: FooterActionsViewModel, @@ -98,6 +98,11 @@ object FooterActionsViewBinder { var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null var previousUserSwitcher: FooterActionsButtonViewModel? = null + // Set the initial visibility on the View directly so that we don't briefly show it for a + // few frames before [viewModel.isVisible] is collected. + view.isInvisible = !viewModel.isVisible.value + + // Listen for ViewModel updates when the View is attached. view.repeatWhenAttached { val attachedScope = this.lifecycleScope diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index 3a9098ab49d36..b3596a254b7d2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -64,7 +64,7 @@ class FooterActionsViewModel( * the UI should still participate to the layout it is included in (i.e. in the View world it * should be INVISIBLE, not GONE). */ - private val _isVisible = MutableStateFlow(true) + private val _isVisible = MutableStateFlow(false) val isVisible: StateFlow = _isVisible.asStateFlow() /** The alpha the UI rendering this ViewModel should have. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 0ab0e2b4b9f48..87ca9dfdae11e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -106,6 +106,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Mock private QSSquishinessController mSquishinessController; @Mock private FooterActionsViewModel mFooterActionsViewModel; @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory; + @Mock private FooterActionsViewBinder mFooterActionsViewBinder; @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; @Mock private FeatureFlags mFeatureFlags; private View mQsFragmentView; @@ -558,6 +559,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory, + mFooterActionsViewBinder, mLargeScreenShadeInterpolator, mFeatureFlags); } @@ -584,7 +586,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader); when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext)); when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer( - invocation -> FooterActionsViewBinder.create(mContext)); + invocation -> new FooterActionsViewBinder().create(mContext)); } private void setUpInflater() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 59f0d967596b7..2cc6709d0f37d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -376,13 +376,13 @@ class FooterActionsViewModelTest : SysuiTestCase() { @Test fun isVisible() { val underTest = utils.footerActionsViewModel() - assertThat(underTest.isVisible.value).isTrue() - - underTest.onVisibilityChangeRequested(visible = false) assertThat(underTest.isVisible.value).isFalse() underTest.onVisibilityChangeRequested(visible = true) assertThat(underTest.isVisible.value).isTrue() + + underTest.onVisibilityChangeRequested(visible = false) + assertThat(underTest.isVisible.value).isFalse() } @Test