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

Commit a0fbcf44 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB Refactor] Connect the old pipeline and new pipeline visibility

calculations together for wifi.

The _new_ pipeline has the source of truth for whether the wifi icon
*should* be visible. But, the _old_ view system has the source of truth
for whether the wifi icon *can* be visible (i.e. if there's enough
space for it).

This CL hooks up those two systems together by having
`WifiViewBinder#bind` return a `Binding` interface implementation,
similar to `KeyguardButtomAreaViewBinder#bind`. This allows them to
communicate with each other and correctly change the view's visibility.

Bug: 238425913
Test: manual: Go into airplane mode and verify that the wifi icon
disappears completely and there's no empty gap in the status bar.
Test: manual: Go into a state where there's not enough room for the wifi
icon and verify that just a tinted dot shows up. See demo video in
b/238425913#comment30.
Test: statusbar.pipeline tests

Change-Id: I9d421a4afb503c9327578f777561a4d38bf2fd48
parent 1cb44532
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -131,6 +131,9 @@
    <!-- For StatusIconContainer to tag its icon views -->
    <item type="id" name="status_bar_view_state_tag" />

    <!-- Status bar -->
    <item type="id" name="status_bar_dot" />

    <!-- Default display cutout on the physical top of screen -->
    <item type="id" name="display_cutout" />
    <item type="id" name="display_cutout_left" />
+8 −0
Original line number Diff line number Diff line
@@ -38,7 +38,15 @@ public interface StatusIconDisplayable extends DarkReceiver {
    @StatusBarIconView.VisibleState
    int getVisibleState();

    /**
     * Returns true if this icon should be visible if there's space, and false otherwise.
     *
     * Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
     * more icons than space, in which case this icon might just show a dot or might be completely
     * hidden. {@link #getVisibleState} will return the icon's actual visible status.
     */
    boolean isIconVisible();

    default boolean isIconBlocked() {
        return false;
    }
+46 −8
Original line number Diff line number Diff line
@@ -26,10 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -44,6 +49,19 @@ import kotlinx.coroutines.launch
@OptIn(InternalCoroutinesApi::class)
object WifiViewBinder {

    /**
     * Defines interface for an object that acts as the binding between the view and its view-model.
     *
     * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
     */
    interface Binding {
        /** Returns true if the wifi icon should be visible and false otherwise. */
        fun getShouldIconBeVisible(): Boolean

        /** Notifies that the visibility state has changed. */
        fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
    }

    /**
     * Binds the view to the appropriate view-model based on the given location. The view will
     * continue to be updated following updates from the view-model.
@@ -53,8 +71,8 @@ object WifiViewBinder {
        view: ViewGroup,
        wifiViewModel: WifiViewModel,
        location: StatusBarLocation,
    ) {
        when (location) {
    ): Binding {
        return when (location) {
            StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
            StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
            StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
@@ -66,8 +84,10 @@ object WifiViewBinder {
    private fun bind(
        view: ViewGroup,
        viewModel: LocationBasedWifiViewModel,
    ) {
    ): Binding {
        val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
        val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
        val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
        val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
        val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
        val activityContainerView = view.requireViewById<View>(R.id.inout_container)
@@ -75,14 +95,21 @@ object WifiViewBinder {
        view.isVisible = true
        iconView.isVisible = true

        // TODO(b/238425913): We should log this visibility state.
        @StatusBarIconView.VisibleState
        val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)

        view.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
                        // TODO(b/238425913): Right now, if !isVisible, there's just an empty space
                        //  where the wifi icon would be. We need to pipe isVisible through to
                        //   [ModernStatusBarWifiView.isIconVisible], which is what actually makes
                        //   the view GONE.
                    visibilityState.collect { visibilityState ->
                        groupView.isVisible = visibilityState == STATE_ICON
                        dotView.isVisible = visibilityState == STATE_DOT
                    }
                }

                launch {
                    viewModel.wifiIcon.collect { wifiIcon ->
                        view.isVisible = wifiIcon != null
                        wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
                    }
@@ -94,6 +121,7 @@ object WifiViewBinder {
                        iconView.imageTintList = tintList
                        activityInView.imageTintList = tintList
                        activityOutView.imageTintList = tintList
                        dotView.setDecorColor(tint)
                    }
                }

@@ -116,5 +144,15 @@ object WifiViewBinder {
                }
            }
        }

        return object : Binding {
            override fun getShouldIconBeVisible(): Boolean {
                return viewModel.wifiIcon.value != null
            }

            override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
                visibilityState.value = state
            }
        }
    }
}
+44 −11
Original line number Diff line number Diff line
@@ -19,11 +19,13 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.statusbar.BaseStatusBarWifiView
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -38,6 +40,17 @@ class ModernStatusBarWifiView(
) : BaseStatusBarWifiView(context, attrs) {

    private lateinit var slot: String
    private lateinit var binding: WifiViewBinder.Binding

    @StatusBarIconView.VisibleState
    private var iconVisibleState: Int = STATE_HIDDEN
        set(value) {
            if (field == value) {
                return
            }
            field = value
            binding.onVisibilityStateChanged(value)
        }

    override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
        // TODO(b/238425913)
@@ -54,23 +67,44 @@ class ModernStatusBarWifiView(
    }

    override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
        // TODO(b/238425913)
        iconVisibleState = state
    }

    @StatusBarIconView.VisibleState
    override fun getVisibleState(): Int {
        // TODO(b/238425913)
        return STATE_ICON
        return iconVisibleState
    }

    override fun isIconVisible(): Boolean {
        // TODO(b/238425913)
        return true
        return binding.getShouldIconBeVisible()
    }

    private fun initView(
        slotName: String,
        wifiViewModel: WifiViewModel,
        location: StatusBarLocation,
    ) {
        slot = slotName
        initDotView()
        binding = WifiViewBinder.bind(this, wifiViewModel, location)
    }

    // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
    private fun initDotView() {
        // TODO(b/238425913): Could we just have this dot view be part of
        //   R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
        //   manually? Would that not work with animations?
        val dotView = StatusBarIconView(mContext, slot, null).also {
            it.id = R.id.status_bar_dot
            // Hard-code this view to always be in the DOT state so that whenever it's visible it
            // will show a dot
            it.visibleState = STATE_DOT
        }

    /** Set the slot name for this view. */
    private fun setSlot(slotName: String) {
        this.slot = slotName
        val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
        val lp = LayoutParams(width, width)
        lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
        addView(dotView, lp)
    }

    companion object {
@@ -89,8 +123,7 @@ class ModernStatusBarWifiView(
                LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
                    as ModernStatusBarWifiView
                ).also {
                    it.setSlot(slot)
                    WifiViewBinder.bind(it, wifiViewModel, location)
                    it.initView(slot, wifiViewModel, location)
                }
        }
    }
+2 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.graphics.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

/**
 * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
@@ -27,7 +28,7 @@ import kotlinx.coroutines.flow.Flow
 */
class HomeWifiViewModel(
    statusBarPipelineFlags: StatusBarPipelineFlags,
    wifiIcon: Flow<Icon?>,
    wifiIcon: StateFlow<Icon?>,
    isActivityInViewVisible: Flow<Boolean>,
    isActivityOutViewVisible: Flow<Boolean>,
    isActivityContainerVisible: Flow<Boolean>,
Loading