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

Commit 1b9b7b46 authored by Matthew DeVore's avatar Matthew DeVore
Browse files

DisplayTopology: use correct layout listener

The GlobalLayoutListener was not being informed of the pane's size
change at the correct time. It was causing a paneless UI to flash very
briefly when opening the ExternalDisplayPreferenceFragment. This was
hard to see in most situations, but when replacing the fragment with
a new ExternalDisplayPreferenceFragment it became very obvious (this
may be necessary to implement b/409354332).

We also got informed twice, because the resize of the pane when creating
a new TopologyScale caused a new notification. This made us need a
flag to indicate whether refresh was necessary. Instead, we use the
old/new values in the OnLayoutListener to decide if the scale needs to
be reconstructed.

Flag: com.android.settings.flags.display_topology_pane_in_display_list
Bug: b/400934884
Bug: b/409354332
Test: verify with a Log call that refreshPane is called ONCE when opening the fragment
Test: verify with a Log call that refreshPane is called ONCE when changing width of fragment
Change-Id: Ibde4efae2d72d18c49535e75b00a13d6fc07a75e
parent 6dc042d6
Loading
Loading
Loading
Loading
+26 −22
Original line number Diff line number Diff line
@@ -25,8 +25,9 @@ import android.graphics.PointF
import android.graphics.RectF
import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.util.Log
import android.view.MotionEvent
import android.view.ViewTreeObserver
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView

@@ -43,8 +44,7 @@ import kotlin.math.abs
 * when there is one or more extended display attached.
 */
class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
        : Preference(injector.context!!), ViewTreeObserver.OnGlobalLayoutListener,
          GroupSectionDividerMixin {
        : Preference(injector.context!!), GroupSectionDividerMixin {
    @VisibleForTesting lateinit var mPaneContent : FrameLayout
    @VisibleForTesting lateinit var mPaneHolder : FrameLayout
    @VisibleForTesting lateinit var mTopologyHint : TextView
@@ -63,14 +63,25 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
     */
    @VisibleForTesting val accidentalDragTimeLimitMs = 800L

    /**
     * This is needed to prevent a repopulation of the pane causing another
     * relayout and vice-versa ad infinitum.
     */
    private var mPaneNeedsRefresh = false

    private val mTopologyListener = Consumer<DisplayTopology> { applyTopology(it) }

    private val mPaneContentLayoutListener = object : View.OnLayoutChangeListener {
        override fun onLayoutChange(v: View,
                newLeft: Int, newTop: Int, newRight: Int, newBottom: Int,
                oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
            val oldWidth = oldRight - oldLeft
            val newWidth = newRight - newLeft
            // The width will change e.g. when first displaying the topology UI (oldWidth is 0) or
            // when the window is resized. We ignore when the height is changed because we don't
            // specify the height in terms of layout, but specify it algorithmically via
            // TopologyScale, which uses the width and the topology to calculate a height.
            if (oldWidth != newWidth) {
                Log.i(TAG, "Width changed from $oldWidth to $newWidth - refresh pane")
                refreshPane()
            }
        }
    }

    init {
        layoutResource = R.layout.display_topology_preference

@@ -90,20 +101,16 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
            if (newPane == mPaneContent) {
                return
            }
            mPaneContent.viewTreeObserver.removeOnGlobalLayoutListener(this)
            mPaneContent.removeOnLayoutChangeListener(mPaneContentLayoutListener)
        }
        mPaneContent = newPane
        mPaneHolder = holder.itemView as FrameLayout
        mTopologyHint = holder.findViewById(R.id.topology_hint) as TextView
        mPaneContent.viewTreeObserver.addOnGlobalLayoutListener(this)
        mPaneContent.addOnLayoutChangeListener(mPaneContentLayoutListener)
    }

    override fun onAttached() {
        super.onAttached()
        // We don't know if topology changes happened when we were detached, as it is impossible to
        // listen at that time (we must remove listeners when detaching). Setting this flag makes
        // the following onGlobalLayout call refresh the pane.
        mPaneNeedsRefresh = true
        injector.registerTopologyListener(mTopologyListener)
    }

@@ -118,13 +125,6 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
        injector.unregisterTopologyListener(mTopologyListener)
    }

    override fun onGlobalLayout() {
        if (mPaneNeedsRefresh) {
            mPaneNeedsRefresh = false
            refreshPane()
        }
    }

    /**
     * Holds information about the current system topology.
     * @param positions list of displays comprised of the display ID and position
@@ -350,4 +350,8 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)

        return true
    }

    companion object {
        val TAG = "DisplayTopologyPreference"
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -117,7 +117,7 @@ class DisplayTopologyPreferenceTest {
    @Test
    fun disabledTopology() {
        preference.onAttached()
        preference.onGlobalLayout()
        preference.refreshPane()

        assertThat(preference.mPaneContent.childCount).isEqualTo(0)
        // TODO(b/352648432): update test when we show the main display even when
@@ -180,7 +180,7 @@ class DisplayTopologyPreferenceTest {
        preference.mPaneContent.right = 640

        preference.onAttached()
        preference.onGlobalLayout()
        preference.refreshPane()
    }

    /**