Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b978757 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Fix flicker of QS footer actions when restarting SystemUI (1/2)

This CL fixes a flicker that could happen with the footer actions View
when SystemUI is starting. This CL is changing the default visibility of
that View to INVISIBLE instead of VISIBLE.

I also had to make FooterActionsViewBinder a class instead of a static
object to make it mockable in QSFragmentTest.

Bug: 263699931
Test: atest FooterActionsViewModelTest
Test: Manual, crash SystemUI
Change-Id: I0f4344cf52dcb00a8b5b38d8a259c0188f13433d
parent d63a1d16
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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;
        }
+9 −4
Original line number Diff line number Diff line
@@ -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

+1 −1
Original line number Diff line number Diff line
@@ -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<Boolean> = _isVisible.asStateFlow()

    /** The alpha the UI rendering this ViewModel should have. */
+3 −1
Original line number Diff line number Diff line
@@ -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() {
+3 −3
Original line number Diff line number Diff line
@@ -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