diff --git a/res/drawable/display_block_selection_marker_background.xml b/res/drawable/display_block_selection_marker_background.xml
new file mode 100644
index 00000000000..54f2bd2e2a0
--- /dev/null
+++ b/res/drawable/display_block_selection_marker_background.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
diff --git a/res/drawable/display_block_unselected_background.xml b/res/drawable/display_block_unselected_background.xml
new file mode 100644
index 00000000000..7c964717e2a
--- /dev/null
+++ b/res/drawable/display_block_unselected_background.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
index 3892138bc25..df061b74193 100644
--- a/res/values-night/colors.xml
+++ b/res/values-night/colors.xml
@@ -82,5 +82,6 @@
@color/settingslib_color_charcoal
+ @android:color/system_secondary_dark
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 6e3f96fc32c..5c516d59024 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -224,4 +224,5 @@
@color/settingslib_color_grey100
+ @android:color/system_secondary_light
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 3359758d8bc..1f4a555b0eb 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -550,4 +550,7 @@
24dp
+ 5dp
+ 2dp
+ 10dp
diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
index cc075ebe76d..78f29e4cbc0 100644
--- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
+++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt
@@ -20,12 +20,15 @@ import android.app.WallpaperManager
import com.android.settings.R
import android.content.Context
+import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Point
import android.graphics.PointF
import android.graphics.RectF
+import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.LayerDrawable
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayTopology
import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
@@ -168,36 +171,54 @@ class TopologyScale(
const val TOPOLOGY_PREFERENCE_KEY = "display_topology_preference"
-/** Padding in pane coordinate pixels on each side of a display block. */
-const val BLOCK_PADDING = 2f
-
/** Represents a draggable block in the topology pane. */
class DisplayBlock(context : Context) : Button(context) {
+ @VisibleForTesting var mSelectedImage: Drawable = ColorDrawable(Color.BLACK)
+ @VisibleForTesting var mUnselectedImage: Drawable = ColorDrawable(Color.BLACK)
+
+ private val mSelectedBg = context.getDrawable(
+ R.drawable.display_block_selection_marker_background)!!
+ private val mUnselectedBg = context.getDrawable(
+ R.drawable.display_block_unselected_background)!!
+ private val mInsetPx = context.resources.getDimensionPixelSize(R.dimen.display_block_padding)
+
init {
isScrollContainer = false
isVerticalScrollBarEnabled = false
isHorizontalScrollBarEnabled = false
+
+ // Prevents shadow from appearing around edge of button.
+ stateListAnimator = null
}
/** Sets position of the block given unpadded coordinates. */
fun place(topLeft: PointF) {
- x = topLeft.x + BLOCK_PADDING
- y = topLeft.y + BLOCK_PADDING
+ x = topLeft.x
+ y = topLeft.y
}
- val unpaddedX: Float
- get() = x - BLOCK_PADDING
+ fun setWallpaper(wallpaper: Bitmap?) {
+ val wallpaperDrawable = BitmapDrawable(context.resources, wallpaper ?: return)
- val unpaddedY: Float
- get() = y - BLOCK_PADDING
+ fun framedBy(bg: Drawable): Drawable =
+ LayerDrawable(arrayOf(wallpaperDrawable, bg)).apply {
+ setLayerInsetRelative(0, mInsetPx, mInsetPx, mInsetPx, mInsetPx)
+ }
+ mSelectedImage = framedBy(mSelectedBg)
+ mUnselectedImage = framedBy(mUnselectedBg)
+ }
+
+ fun setHighlighted(value: Boolean) {
+ background = if (value) mSelectedImage else mUnselectedImage
+ }
/** Sets position and size of the block given unpadded bounds. */
fun placeAndSize(bounds : RectF, scale : TopologyScale) {
val topLeft = scale.displayToPaneCoor(bounds.left, bounds.top)
val bottomRight = scale.displayToPaneCoor(bounds.right, bounds.bottom)
val layout = layoutParams
- layout.width = (bottomRight.x - topLeft.x - BLOCK_PADDING * 2f).toInt()
- layout.height = (bottomRight.y - topLeft.y - BLOCK_PADDING * 2f).toInt()
+ layout.width = (bottomRight.x - topLeft.x).toInt()
+ layout.height = (bottomRight.y - topLeft.y).toInt()
layoutParams = layout
place(topLeft)
}
@@ -284,8 +305,8 @@ class DisplayTopologyPreference(context : Context)
get() = displayManager.displayTopology
set(value) { displayManager.displayTopology = value }
- open val wallpaper : Drawable
- get() = WallpaperManager.getInstance(context).drawable ?: ColorDrawable(Color.BLACK)
+ open val wallpaper: Bitmap?
+ get() = WallpaperManager.getInstance(context).bitmap
open fun registerTopologyListener(listener: Consumer) {
displayManager.registerTopologyListener(context.mainExecutor, listener)
@@ -386,14 +407,20 @@ class DisplayTopologyPreference(context : Context)
}
}
+ var wallpaperBitmap : Bitmap? = null
+
newBounds.forEach { (id, pos) ->
val block = recycleableBlocks.removeFirstOrNull() ?: DisplayBlock(context).apply {
+ if (wallpaperBitmap == null) {
+ wallpaperBitmap = injector.wallpaper
+ }
// We need a separate wallpaper Drawable for each display block, since each needs to
// be drawn at a separate size.
- background = injector.wallpaper
+ setWallpaper(wallpaperBitmap)
mPaneContent.addView(this)
}
+ block.setHighlighted(false)
block.placeAndSize(pos, scaling)
block.setOnTouchListener { view, ev ->
@@ -422,12 +449,15 @@ class DisplayTopologyPreference(context : Context)
val stationaryDisps = positions.filter { it.first != displayId }
+ mDrag?.display?.setHighlighted(false)
+ block.setHighlighted(true)
+
// We have to use rawX and rawY for the coordinates since the view receiving the event is
// also the view that is moving. We need coordinates relative to something that isn't
// moving, and the raw coordinates are relative to the screen.
mDrag = BlockDrag(
stationaryDisps.toList(), block, displayId, displayPos.width(), displayPos.height(),
- ev.rawX - block.unpaddedX, ev.rawY - block.unpaddedY)
+ ev.rawX - block.x, ev.rawY - block.y)
// Prevents a container of this view from intercepting the touch events in the case the
// pointer moves outside of the display block or the pane.
@@ -454,9 +484,10 @@ class DisplayTopologyPreference(context : Context)
val drag = mDrag ?: return false
val topology = mTopologyInfo ?: return false
mPaneContent.requestDisallowInterceptTouchEvent(false)
+ drag.display.setHighlighted(false)
val newCoor = topology.scaling.paneToDisplayCoor(
- drag.display.unpaddedX, drag.display.unpaddedY)
+ drag.display.x, drag.display.y)
val newTopology = topology.topology.copy()
val newPositions = drag.stationaryDisps.map { (id, pos) -> id to PointF(pos.left, pos.top) }
.plus(drag.displayId to newCoor)
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
index d149605a748..08abfe2dc5e 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
+++ b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt
@@ -21,9 +21,9 @@ import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT
import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
import android.content.Context
+import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.Drawable
-import android.graphics.drawable.ColorDrawable
import android.hardware.display.DisplayTopology
import android.view.MotionEvent
import android.view.View
@@ -50,7 +50,8 @@ class DisplayTopologyPreferenceTest {
val injector = TestInjector(context)
val rootView = View.inflate(context, preference.layoutResource, /*parent=*/ null)
val holder = PreferenceViewHolder.createInstanceForTests(rootView)
- val wallpaper = ColorDrawable(Color.MAGENTA)
+ val wallpaper = Bitmap.createBitmap(
+ intArrayOf(Color.MAGENTA), /*width=*/ 1, /*height=*/ 1, Bitmap.Config.RGB_565)
init {
preference.injector = injector
@@ -60,14 +61,14 @@ class DisplayTopologyPreferenceTest {
class TestInjector(context : Context) : DisplayTopologyPreference.Injector(context) {
var topology: DisplayTopology? = null
- var systemWallpaper: Drawable? = null
+ var systemWallpaper: Bitmap? = null
var topologyListener: Consumer? = null
override var displayTopology : DisplayTopology?
get() = topology
set(value) { topology = value }
- override val wallpaper : Drawable
+ override val wallpaper: Bitmap?
get() = systemWallpaper!!
override fun registerTopologyListener(listener: Consumer) {
@@ -164,14 +165,14 @@ class DisplayTopologyPreferenceTest {
val (childBlock, rootBlock) = setupTwoDisplays()
// After accounting for padding, child should be half the length of root in each dimension.
- assertThat(childBlock.layoutParams.width + BLOCK_PADDING)
+ assertThat(childBlock.layoutParams.width)
.isEqualTo(rootBlock.layoutParams.width / 2)
- assertThat(childBlock.layoutParams.height + BLOCK_PADDING)
+ assertThat(childBlock.layoutParams.height)
.isEqualTo(rootBlock.layoutParams.height / 2)
assertThat(childBlock.y).isGreaterThan(rootBlock.y)
- assertThat(childBlock.background).isEqualTo(wallpaper)
- assertThat(rootBlock.background).isEqualTo(wallpaper)
- assertThat(rootBlock.x - BLOCK_PADDING * 2)
+ assertThat(childBlock.background).isEqualTo(childBlock.mUnselectedImage)
+ assertThat(rootBlock.background).isEqualTo(rootBlock.mUnselectedImage)
+ assertThat(rootBlock.x)
.isEqualTo(childBlock.x + childBlock.layoutParams.width)
assertThat(preference.mTopologyHint.text)
@@ -180,7 +181,7 @@ class DisplayTopologyPreferenceTest {
@Test
fun dragDisplayDownward() {
- val (leftBlock, rightBlock) = setupTwoDisplays()
+ val (leftBlock, _) = setupTwoDisplays()
val downEvent = MotionEventBuilder.newBuilder()
.setPointer(0f, 0f)
@@ -191,7 +192,7 @@ class DisplayTopologyPreferenceTest {
// coordinates. The original offset is 42, so the new offset will be 42 + 40.
val moveEvent = MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_MOVE)
- .setPointer(0f, leftBlock.layoutParams.height / 2f + BLOCK_PADDING)
+ .setPointer(0f, leftBlock.layoutParams.height / 2f)
.build()
val upEvent = MotionEventBuilder.newBuilder().setAction(MotionEvent.ACTION_UP).build()
@@ -220,7 +221,7 @@ class DisplayTopologyPreferenceTest {
val moveEvent = MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_MOVE)
.setPointer(
- -leftBlock.layoutParams.width - 2f * BLOCK_PADDING,
+ -leftBlock.layoutParams.width.toFloat(),
-leftBlock.layoutParams.height / 2f)
.build()
@@ -278,8 +279,8 @@ class DisplayTopologyPreferenceTest {
// Look for a display with the same unusual aspect ratio as the one we've added.
val expectedAspectRatio = 300f/320f
assertThat(paneChildren
- .map { (it.layoutParams.width.toFloat() + BLOCK_PADDING*2) /
- (it.layoutParams.height.toFloat() + BLOCK_PADDING*2) }
+ .map { it.layoutParams.width.toFloat() /
+ it.layoutParams.height.toFloat() }
.filter { abs(it - expectedAspectRatio) < 0.001f }
).hasSize(1)
}
@@ -305,7 +306,7 @@ class DisplayTopologyPreferenceTest {
assertThat(paneChildren).hasSize(1)
val block = paneChildren[0]
- val origY = block.unpaddedY
+ val origY = block.y
block.dispatchTouchEvent(MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_DOWN)
@@ -316,21 +317,21 @@ class DisplayTopologyPreferenceTest {
.setPointer(0f, 30f)
.build())
- assertThat(block.unpaddedY).isWithin(0.01f).of(origY)
+ assertThat(block.y).isWithin(0.01f).of(origY)
block.dispatchTouchEvent(MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_UP)
.build())
// Block should be back to original position.
- assertThat(block.unpaddedY).isWithin(0.01f).of(origY)
+ assertThat(block.y).isWithin(0.01f).of(origY)
}
@Test
fun updatedTopologyCancelsDragIfNonTrivialChange() {
- val (leftBlock, rightBlock) = setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)
+ val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(142.17f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(142.17f)
leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_DOWN)
@@ -340,29 +341,45 @@ class DisplayTopologyPreferenceTest {
.setAction(MotionEvent.ACTION_MOVE)
.setPointer(0f, 30f)
.build())
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(172.17f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(172.17f)
// Offset is only different by 0.5 dp, so the drag will not cancel.
injector.topology = twoDisplayTopology(POSITION_LEFT, /* childOffset= */ 41.5f)
injector.topologyListener!!.accept(injector.topology!!)
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(172.17f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(172.17f)
// Move block farther downward.
leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_MOVE)
.setPointer(0f, 50f)
.build())
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(192.17f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(192.17f)
injector.topology = twoDisplayTopology(POSITION_LEFT, /* childOffset= */ 20f)
injector.topologyListener!!.accept(injector.topology!!)
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(125.67f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(125.67f)
// Another move in the opposite direction should not move the left block.
leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
.setAction(MotionEvent.ACTION_MOVE)
.setPointer(0f, -20f)
.build())
- assertThat(leftBlock.unpaddedY).isWithin(0.01f).of(125.67f)
+ assertThat(leftBlock.y).isWithin(0.01f).of(125.67f)
+ }
+
+ @Test
+ fun highlightDuringDrag() {
+ val (leftBlock, _) = setupTwoDisplays(POSITION_LEFT, /* childOffset= */ 42f)
+
+ assertThat(leftBlock.background).isEqualTo(leftBlock.mUnselectedImage)
+ leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
+ .setAction(MotionEvent.ACTION_DOWN)
+ .setPointer(0f, 0f)
+ .build())
+ assertThat(leftBlock.background).isEqualTo(leftBlock.mSelectedImage)
+ leftBlock.dispatchTouchEvent(MotionEventBuilder.newBuilder()
+ .setAction(MotionEvent.ACTION_UP)
+ .build())
+ assertThat(leftBlock.background).isEqualTo(leftBlock.mUnselectedImage)
}
}