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

Commit baff80a6 authored by Nergi Rahardi's avatar Nergi Rahardi Committed by Android (Google) Code Review
Browse files

Merge changes Ibd58d038,I4977aac3 into main

* changes:
  [CD Settings] Use ViewOutlineProvider to draw curved border
  [CD Settings] Fetch logicalSize from DisplayManager
parents e4ac78a2 c6519410
Loading
Loading
Loading
Loading
+0 −10
Original line number Diff line number Diff line
@@ -17,16 +17,6 @@

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- A non-rounded rectangle is needed to prevent the enclosed image from
         leaking out behind the rounded corners. -->
    <item>
        <shape android:shape="rectangle">
            <stroke
                android:color="@color/display_topology_background_color"
                android:width="@dimen/display_block_padding" />
        </shape>
    </item>

    <item>
        <shape android:shape="rectangle">
            <stroke
+8 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.RemoteException
import android.os.SystemProperties
import android.util.DisplayMetrics
import android.util.Log
import android.util.Size
import android.view.Display
import android.view.Display.INVALID_DISPLAY
import android.view.DisplayInfo
@@ -290,6 +291,13 @@ open class ConnectedDisplayInjector(open val context: Context?) {
        }
    }

    open fun getLogicalSize(displayId: Int): Size? {
        val display =  displayManager?.getDisplay(displayId)?: return null
        val displayInfo = DisplayInfo()
        display.getDisplayInfo(displayInfo)
        return Size(displayInfo.logicalWidth, displayInfo.logicalHeight)
    }

    open fun registerTopologyListener(listener: Consumer<DisplayTopology>) {
        val executor = context?.mainExecutor
        if (executor != null) displayManager?.registerTopologyListener(executor, listener)
+12 −6
Original line number Diff line number Diff line
@@ -16,26 +16,25 @@

package com.android.settings.connecteddevice.display

import com.android.settings.R

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Outline
import android.graphics.PointF
import android.util.Log
import android.view.SurfaceControl
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.FrameLayout

import androidx.annotation.VisibleForTesting
import com.android.settings.R

/** Represents a draggable block in the topology pane. */
class DisplayBlock(val injector: ConnectedDisplayInjector) : FrameLayout(injector.context!!) {
    @VisibleForTesting
    val mHighlightPx = context.resources.getDimensionPixelSize(
            R.dimen.display_block_highlight_width)
    val cornerRadiusPx = context.resources.getDimensionPixelSize(
        R.dimen.display_block_corner_radius)

    private var mDisplayId: Int? = null

@@ -183,6 +182,13 @@ class DisplayBlock(val injector: ConnectedDisplayInjector) : FrameLayout(injecto
            mWallpaperView.layoutParams = it
        }

        mWallpaperView.outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                outline.setRoundRect(0, 0, view.width, view.height, cornerRadiusPx.toFloat())
            }
        }
        mWallpaperView.clipToOutline = true

        // The other two child views are MATCH_PARENT by default so will resize to fill up the
        // FrameLayout.
    }
+55 −38
Original line number Diff line number Diff line
@@ -16,27 +16,21 @@

package com.android.settings.connecteddevice.display

import com.android.settings.R
import com.android.settingslib.widget.GroupSectionDividerMixin

import android.content.Context
import android.graphics.Bitmap
import android.graphics.PointF
import android.graphics.RectF
import android.hardware.display.DisplayTopology
import android.util.DisplayMetrics
import android.util.Log
import android.util.Size
import android.view.MotionEvent
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView

import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder

import com.android.settings.R
import com.android.settingslib.widget.GroupSectionDividerMixin
import java.util.function.Consumer

import kotlin.math.abs

/**
@@ -213,11 +207,42 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
            recycleableBlocks.add(mPaneContent.getChildAt(i) as DisplayBlock)
        }

        val idToNode = topology.allNodesIdMap()
        val topologyLogicalDisplaySize =
            idToNode.filter { it.key != null && it.value != null }
                .map { it.key!! to Size(it.value.logicalWidth, it.value.logicalHeight) }
                .toMap()
        val scaling = TopologyScale(
            mPaneContent.width,
                minEdgeLength = DisplayTopology.dpToPx(60f, injector.densityDpi),
                maxEdgeLength = DisplayTopology.dpToPx(256f, injector.densityDpi),
                newBounds.map { it.second }.toList())
            minEdgeLength = DisplayTopology.dpToPx(MIN_EDGE_LENGTH_DP, injector.densityDpi),
            maxEdgeLength = DisplayTopology.dpToPx(MAX_EDGE_LENGTH_DP, injector.densityDpi),
            newBounds.map { it.second })
        setupDisplayPaneAndBlocks(scaling, newBounds, topologyLogicalDisplaySize)
        mTopologyInfo = TopologyInfo(topology, scaling, newBounds)

        // Construct a map containing revealers that we want to keep (keepRevealing). Then create a
        // list comprised of the values of that map as well as new revealers (mRevealedWallpapers).
        val keepRevealing = buildMap<Int, RevealedWallpaper> {
            mRevealedWallpapers.forEach { r ->
                if (idToNode.containsKey(r.displayId)) {
                    put(r.displayId, r)
                } else {
                    r.viewManager.removeView(r.revealer)
                }
            }
        }
        mRevealedWallpapers = idToNode.keys
            .map { keepRevealing.get(it) ?: injector.revealWallpaper(it) }
            .filterNotNull()
            .toList()
    }

    private fun setupDisplayPaneAndBlocks(
        scaling: TopologyScale,
        newBounds: List<Pair<Int, RectF>>,
        topologyLogicalDisplaySize: Map<Int, Size>
    ) {
        // Resize pane holder
        mPaneHolder.layoutParams.let {
            val newHeight = scaling.paneHeight.toInt()
            if (it.height != newHeight) {
@@ -226,16 +251,25 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
            }
        }

        val idToNode = topology.allNodesIdMap()
        // Setup display blocks
        val recycleableBlocks = ArrayDeque<DisplayBlock>()
        for (i in 0..mPaneContent.childCount - 1) {
            recycleableBlocks.add(mPaneContent.getChildAt(i) as DisplayBlock)
        }
        newBounds.forEach { (id, pos) ->
            val block = recycleableBlocks.removeFirstOrNull() ?: DisplayBlock(injector).apply {
                mPaneContent.addView(this)
            }

            idToNode.get(id)?.let {
            // First check from DisplayTopology for quick lookup on logical display size. If display
            // is not in topology, then query from DisplayInfo.
            val logicalDisplaySize =
                topologyLogicalDisplaySize.get(id) ?: injector.getLogicalSize(id)
            logicalDisplaySize?.let {
                val topLeft = scaling.displayToPaneCoor(pos.left, pos.top)
                val bottomRight = scaling.displayToPaneCoor(pos.right, pos.bottom)
                block.reset(id, topLeft, bottomRight, (bottomRight.x - topLeft.x) / it.logicalWidth)
                block.reset(
                    id, topLeft, bottomRight, (bottomRight.x - topLeft.x) / it.width
                )
            }
            block.setOnTouchListener { view, ev ->
                when (ev.actionMasked) {
@@ -248,25 +282,6 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
        }
        mPaneContent.removeViews(newBounds.size, recycleableBlocks.size)
        mTimesRefreshedBlocks++

        mTopologyInfo = TopologyInfo(topology, scaling, newBounds)

        // Construct a map containing revealers that we want to keep (keepRevealing). Then create a
        // list comprised of the values of that map as well as new revealers (mRevealedWallpapers).
        val keepRevealing = buildMap<Int, RevealedWallpaper> {
            mRevealedWallpapers.forEach { r ->
                if (idToNode.containsKey(r.displayId)) {
                    put(r.displayId, r)
                } else {
                    r.viewManager.removeView(r.revealer)
                }
            }
        }
        mRevealedWallpapers = idToNode.keys
            .map { keepRevealing.get(it) ?: injector.revealWallpaper(it) }
            .filterNotNull()
            .toList()

        // Cancel the drag if one is in progress.
        mDrag = null
    }
@@ -351,7 +366,9 @@ class DisplayTopologyPreference(val injector: ConnectedDisplayInjector)
        return true
    }

    companion object {
        val TAG = "DisplayTopologyPreference"
    private companion object {
        private val MIN_EDGE_LENGTH_DP = 60f
        private val MAX_EDGE_LENGTH_DP = 256f
        private val TAG = "DisplayTopologyPreference"
    }
}
+53 −40
Original line number Diff line number Diff line
@@ -16,15 +16,14 @@

package com.android.settings.connecteddevice.display

import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP

import android.content.Context
import android.graphics.Color
import android.graphics.RectF
import android.hardware.display.DisplayTopology
import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
import android.util.DisplayMetrics
import android.util.Size
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -33,14 +32,10 @@ import android.widget.FrameLayout
import androidx.preference.PreferenceViewHolder
import androidx.test.core.app.ApplicationProvider
import androidx.test.core.view.MotionEventBuilder

import com.android.settings.R
import com.google.common.truth.Truth.assertThat

import java.util.function.Consumer

import kotlin.math.abs

import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -58,6 +53,7 @@ class DisplayTopologyPreferenceTest {
    }

    class TestInjector(context : Context) : ConnectedDisplayInjector(context) {
        var displaysSize = mutableMapOf<Int, Size>()
        var topology: DisplayTopology? = null

        var topologyListener: Consumer<DisplayTopology>? = null
@@ -73,6 +69,10 @@ class DisplayTopologyPreferenceTest {

        override val densityDpi = DisplayMetrics.DENSITY_DEFAULT

        override fun getLogicalSize(displayId: Int): Size? {
            return displaysSize.get(displayId)
        }

        override fun registerTopologyListener(listener: Consumer<DisplayTopology>) {
            if (topologyListener != null) {
                throw IllegalStateException(
@@ -144,28 +144,30 @@ class DisplayTopologyPreferenceTest {
                .map { preference.mPaneContent.getChildAt(it) as DisplayBlock }
                .toList()

    fun singleDisplayTopology(): DisplayTopology {
    fun setupSingleDisplay() {
        val primaryId = 22;

        val root = DisplayTopology.TreeNode(
                primaryId, /* logicalWidth= */ 200, /* logicalHeight= */ 160,
                primaryId, DISPLAY_SIZE_1.width, DISPLAY_SIZE_1.height,
                /* logicalDensity= */ 160, POSITION_LEFT, /* offset= */ 0f)

        return DisplayTopology(root, primaryId)
        injector.topology = DisplayTopology(root, primaryId)
        injector.displaysSize = mutableMapOf(Pair(primaryId, DISPLAY_SIZE_1))
    }

    fun twoDisplayTopology(childPosition: Int, childOffset: Float): DisplayTopology {
    fun setupTwoDisplays(childPosition: Int, childOffset: Float) {
        val primaryId = 1

        val child = DisplayTopology.TreeNode(
                /* displayId= */ 42, /* logicalWidth= */ 100, /* logicalHeight= */ 80,
                /* displayId= */ 42, DISPLAY_SIZE_2.width, DISPLAY_SIZE_2.height,
                /* logicalDensity= */ 160, childPosition, childOffset)
        val root = DisplayTopology.TreeNode(
                primaryId, /* logicalWidth= */ 200, /* logicalHeight= */ 160,
                primaryId, DISPLAY_SIZE_1.width, DISPLAY_SIZE_1.height,
                /* logicalDensity= */ 160, POSITION_LEFT, /* offset= */ 0f)
        root.addChild(child)

        return DisplayTopology(root, primaryId)
        injector.topology = DisplayTopology(root, primaryId)
        injector.displaysSize = mutableMapOf(Pair(primaryId, DISPLAY_SIZE_1), Pair(42, DISPLAY_SIZE_2))
    }

    /** Uses the topology in the injector to populate and prepare the pane for interaction. */
@@ -187,9 +189,9 @@ class DisplayTopologyPreferenceTest {
     * Sets up a simple topology in the pane with two displays. Returns the left-hand display and
     * right-hand display in order in a list. The right-hand display is the root.
     */
    fun setupTwoDisplays(childPosition: Int = POSITION_LEFT, childOffset: Float = 42f):
    fun setupPaneWithTwoDisplays(childPosition: Int = POSITION_LEFT, childOffset: Float = 42f):
            List<DisplayBlock> {
        injector.topology = twoDisplayTopology(childPosition, childOffset)
        setupTwoDisplays(childPosition, childOffset)

        preparePane()

@@ -210,7 +212,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun twoDisplaysGenerateBlocks() {
        val (childBlock, rootBlock) = setupTwoDisplays()
        val (childBlock, rootBlock) = setupPaneWithTwoDisplays()
        val childBounds = virtualBounds(childBlock)
        val rootBounds = virtualBounds(rootBlock)

@@ -230,7 +232,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun dragDisplayDownward() {
        val (leftBlock, _) = setupTwoDisplays()
        val (leftBlock, _) = setupPaneWithTwoDisplays()

        preference.mTimesRefreshedBlocks = 0

@@ -263,7 +265,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun dragRootDisplayToNewSide() {
        val (leftBlock, rightBlock) = setupTwoDisplays()
        val (leftBlock, rightBlock) = setupPaneWithTwoDisplays()
        val leftBounds = virtualBounds(leftBlock)

        assertThat(injector.revealLog).containsExactly(
@@ -317,7 +319,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun noRefreshForUnmovingDrag() {
        val (leftBlock, rightBlock) = setupTwoDisplays()
        val (leftBlock, rightBlock) = setupPaneWithTwoDisplays()

        preference.mTimesRefreshedBlocks = 0

@@ -341,13 +343,16 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun keepOriginalViewsWhenAddingMore() {
        setupTwoDisplays()
        setupPaneWithTwoDisplays()
        assertThat(injector.revealLog).containsExactly(
                "revealWallpaper invoked for display 1",
                "revealWallpaper invoked for display 42")
        injector.revealLog.clear()
        val childrenBefore = getPaneChildren()
        injector.topology!!.addDisplay(/* displayId= */ 101, 320, 240, /* logicalDensity= */ 160)
        val newDisplayId = 101
        val newDisplaySize = Size(320, 240)
        injector.topology!!.addDisplay(newDisplayId, newDisplaySize.width, newDisplaySize.height, /* logicalDensity= */ 160)
        injector.displaysSize.put(newDisplayId, newDisplaySize)
        preference.refreshPane()
        assertThat(injector.revealLog).containsExactly("revealWallpaper invoked for display 101")
        injector.revealLog.clear()
@@ -358,7 +363,7 @@ class DisplayTopologyPreferenceTest {
        assertThat(childrenAfter.subList(0, 2)).isEqualTo(childrenBefore)

        assertThat(injector.topology!!.removeDisplay(42)).isTrue()
        assertThat(injector.topology!!.removeDisplay(101)).isTrue()
        assertThat(injector.topology!!.removeDisplay(newDisplayId)).isTrue()
        preference.refreshPane()
        assertThat(injector.revealLog).containsExactly(
                "removed wallpaper revealer for display 42",
@@ -367,12 +372,15 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun applyNewTopologyViaListenerUpdate() {
        setupTwoDisplays()
        setupPaneWithTwoDisplays()

        preference.mTimesRefreshedBlocks = 0
        val newDisplayId = 8008
        val newDisplaySize = Size(300, 320)
        val newTopology = injector.topology!!.copy()
        newTopology.addDisplay(/* displayId= */ 8008, /* logicalWidth= */ 300,
                /* logicalHeight= */ 320, /* logicalDensity= */ 160)
        newTopology.addDisplay(newDisplayId, newDisplaySize.width, newDisplaySize.height,
                /* logicalDensity= */ 160)
        injector.displaysSize.put(newDisplayId, newDisplaySize)

        injector.topology = newTopology
        injector.topologyListener!!.accept(newTopology)
@@ -382,7 +390,7 @@ class DisplayTopologyPreferenceTest {
        assertThat(paneChildren).hasSize(3)

        // Look for a display with the same unusual aspect ratio as the one we've added.
        val expectedAspectRatio = 300f/320f
        val expectedAspectRatio = newDisplaySize.width.toFloat() / newDisplaySize.height.toFloat()
        assertThat(paneChildren
                .map { virtualBounds(it) }
                .map { it.width() / it.height() }
@@ -392,11 +400,11 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun ignoreListenerUpdateOfUnchangedTopology() {
        injector.topology = twoDisplayTopology(POSITION_TOP, /* offset= */ 12.0f)
        setupTwoDisplays(POSITION_TOP, /* offset= */ 12.0f)
        preparePane()

        preference.mTimesRefreshedBlocks = 0
        injector.topology = twoDisplayTopology(POSITION_TOP, /* offset= */ 12.1f)
        setupTwoDisplays(POSITION_TOP, /* offset= */ 12.1f)
        injector.topologyListener!!.accept(injector.topology!!)

        assertThat(preference.mTimesRefreshedBlocks).isEqualTo(0)
@@ -404,7 +412,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun cannotMoveSingleDisplay() {
        injector.topology = singleDisplayTopology()
        setupSingleDisplay()
        preparePane()

        val paneChildren = getPaneChildren()
@@ -434,7 +442,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun updatedTopologyCancelsDragIfNonTrivialChange() {
        val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)
        val (leftBlock, _) = setupPaneWithTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)

        assertThat(leftBlock.positionInPane.y).isWithin(0.05f).of(143.76f)

@@ -449,7 +457,7 @@ class DisplayTopologyPreferenceTest {
        assertThat(leftBlock.positionInPane.y).isWithin(0.05f).of(173.76f)

        // Offset is only different by 0.5 dp, so the drag will not cancel.
        injector.topology = twoDisplayTopology(POSITION_LEFT, /* childOffset= */ 41.5f)
        setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 41.5f)
        injector.topologyListener!!.accept(injector.topology!!)

        assertThat(leftBlock.positionInPane.y).isWithin(0.05f).of(173.76f)
@@ -460,7 +468,7 @@ class DisplayTopologyPreferenceTest {
                .build())
        assertThat(leftBlock.positionInPane.y).isWithin(0.05f).of(193.76f)

        injector.topology = twoDisplayTopology(POSITION_LEFT, /* childOffset= */ 20f)
        setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 20f)
        injector.topologyListener!!.accept(injector.topology!!)

        assertThat(leftBlock.positionInPane.y).isWithin(0.05f).of(115.60f)
@@ -474,7 +482,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun highlightDuringDrag() {
        val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)
        val (leftBlock, _) = setupPaneWithTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)

        assertSelected(leftBlock, false)
        leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
@@ -509,7 +517,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun accidentalDrag_LittleAndBriefEnoughToBeAccidental() {
        val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, childOffset = 42f)
        val (leftBlock, _) = setupPaneWithTwoDisplays(POSITION_LEFT, childOffset = 42f)
        val startTime = 424242L
        val startX = leftBlock.x
        val startY = leftBlock.y
@@ -534,7 +542,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun accidentalDrag_TooFarToBeAccidentalXAxis() {
        val (topBlock, _) = setupTwoDisplays(POSITION_TOP, childOffset = -42f)
        val (topBlock, _) = setupPaneWithTwoDisplays(POSITION_TOP, childOffset = -42f)
        val startTime = 88888L
        val startX = topBlock.x

@@ -550,7 +558,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun accidentalDrag_TooFarToBeAccidentalYAxis() {
        val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, childOffset = 42f)
        val (leftBlock, _) = setupPaneWithTwoDisplays(POSITION_LEFT, childOffset = 42f)
        val startTime = 88888L
        val startY = leftBlock.y

@@ -566,7 +574,7 @@ class DisplayTopologyPreferenceTest {

    @Test
    fun accidentalDrag_TooSlowToBeAccidental() {
        val (topBlock, _) = setupTwoDisplays(POSITION_TOP, childOffset = -42f)
        val (topBlock, _) = setupPaneWithTwoDisplays(POSITION_TOP, childOffset = -42f)
        val startTime = 88888L
        val startX = topBlock.x

@@ -579,4 +587,9 @@ class DisplayTopologyPreferenceTest {
        assertThat(topBlock.x).isNotEqualTo(startX)
        assertThat(preference.mTimesRefreshedBlocks).isEqualTo(1)
    }

    private companion object {
        private val DISPLAY_SIZE_1 = Size(200, 160)
        private val DISPLAY_SIZE_2 = Size(100, 80)
    }
}