Merge "LS Wallpaper Sampling for LS Smartspace" into udc-dev am: 77c206e386

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22873896

Change-Id: I1ecd5ea0be00f57ce9db90f4b3009c53ccf7bc69
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Florence Yang
2023-05-16 19:06:20 +00:00
committed by Automerger Merge Worker
7 changed files with 348 additions and 118 deletions

View File

@@ -588,7 +588,7 @@ public final class WallpaperColors implements Parcelable {
int hints = 0; int hints = 0;
double meanLuminance = totalLuminance / pixels.length; double meanLuminance = totalLuminance / pixels.length;
if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels < maxDarkPixels) { if (meanLuminance > BRIGHT_IMAGE_MEAN_LUMINANCE && darkPixels <= maxDarkPixels) {
hints |= HINT_SUPPORTS_DARK_TEXT; hints |= HINT_SUPPORTS_DARK_TEXT;
} }
if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) { if (meanLuminance < DARK_THEME_MEAN_LUMINANCE) {

View File

@@ -1680,14 +1680,14 @@ public class WallpaperManager {
* @hide * @hide
*/ */
public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback, public void addOnColorsChangedListener(@NonNull LocalWallpaperColorConsumer callback,
List<RectF> regions) throws IllegalArgumentException { List<RectF> regions, int which) throws IllegalArgumentException {
for (RectF region : regions) { for (RectF region : regions) {
if (!LOCAL_COLOR_BOUNDS.contains(region)) { if (!LOCAL_COLOR_BOUNDS.contains(region)) {
throw new IllegalArgumentException("Regions must be within bounds " throw new IllegalArgumentException("Regions must be within bounds "
+ LOCAL_COLOR_BOUNDS); + LOCAL_COLOR_BOUNDS);
} }
} }
sGlobals.addOnColorsChangedListener(callback, regions, FLAG_SYSTEM, sGlobals.addOnColorsChangedListener(callback, regions, which,
mContext.getUserId(), mContext.getDisplayId()); mContext.getUserId(), mContext.getDisplayId());
} }

View File

@@ -21,9 +21,9 @@ import android.graphics.Color
import android.graphics.Point import android.graphics.Point
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.util.Log
import android.view.View import android.view.View
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import com.android.systemui.shared.navigationbar.RegionSamplingHelper
import java.io.PrintWriter import java.io.PrintWriter
import java.util.concurrent.Executor import java.util.concurrent.Executor
@@ -31,20 +31,21 @@ import java.util.concurrent.Executor
open class RegionSampler open class RegionSampler
@JvmOverloads @JvmOverloads
constructor( constructor(
val sampledView: View?, val sampledView: View,
mainExecutor: Executor?, mainExecutor: Executor?,
val bgExecutor: Executor?, val bgExecutor: Executor?,
val regionSamplingEnabled: Boolean, val regionSamplingEnabled: Boolean,
val isLockscreen: Boolean = false,
val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView.context),
val updateForegroundColor: UpdateColorCallback, val updateForegroundColor: UpdateColorCallback,
val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context)
) : WallpaperManager.LocalWallpaperColorConsumer { ) : WallpaperManager.LocalWallpaperColorConsumer {
private var regionDarkness = RegionDarkness.DEFAULT private var regionDarkness = RegionDarkness.DEFAULT
private var samplingBounds = Rect() private var samplingBounds = Rect()
private val tmpScreenLocation = IntArray(2) private val tmpScreenLocation = IntArray(2)
@VisibleForTesting var regionSampler: RegionSamplingHelper? = null
private var lightForegroundColor = Color.WHITE private var lightForegroundColor = Color.WHITE
private var darkForegroundColor = Color.BLACK private var darkForegroundColor = Color.BLACK
private val displaySize = Point() @VisibleForTesting val displaySize = Point()
private var initialSampling: WallpaperColors? = null
/** /**
* Sets the colors to be used for Dark and Light Foreground. * Sets the colors to be used for Dark and Light Foreground.
@@ -57,6 +58,36 @@ constructor(
darkForegroundColor = darkColor darkForegroundColor = darkColor
} }
private val layoutChangedListener =
object : View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
// don't pass in negative bounds when region is in transition state
if (sampledView.locationOnScreen[0] < 0 || sampledView.locationOnScreen[1] < 0) {
return
}
val currentViewRect = Rect(left, top, right, bottom)
val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
if (currentViewRect != oldViewRect) {
stopRegionSampler()
startRegionSampler()
}
}
}
/** /**
* Determines which foreground color to use based on region darkness. * Determines which foreground color to use based on region darkness.
* *
@@ -84,40 +115,57 @@ constructor(
/** Start region sampler */ /** Start region sampler */
fun startRegionSampler() { fun startRegionSampler() {
if (!regionSamplingEnabled || sampledView == null) {
if (!regionSamplingEnabled) {
if (DEBUG) Log.d(TAG, "startRegionSampler() | RegionSampling flag not enabled")
return return
} }
val sampledRegion = calculateSampledRegion(sampledView) sampledView.addOnLayoutChangeListener(layoutChangedListener)
val regions = ArrayList<RectF>()
val sampledRegionWithOffset = convertBounds(sampledRegion)
val screenLocationBounds = calculateScreenLocation(sampledView)
if (screenLocationBounds == null) {
if (DEBUG) Log.d(TAG, "startRegionSampler() | passed in null region")
return
}
if (screenLocationBounds.isEmpty) {
if (DEBUG) Log.d(TAG, "startRegionSampler() | passed in empty region")
return
}
val sampledRegionWithOffset = convertBounds(screenLocationBounds)
if ( if (
sampledRegionWithOffset.left < 0.0 || sampledRegionWithOffset.left < 0.0 ||
sampledRegionWithOffset.right > 1.0 || sampledRegionWithOffset.right > 1.0 ||
sampledRegionWithOffset.top < 0.0 || sampledRegionWithOffset.top < 0.0 ||
sampledRegionWithOffset.bottom > 1.0 sampledRegionWithOffset.bottom > 1.0
) { ) {
android.util.Log.e( if (DEBUG)
"RegionSampler", Log.d(
"view out of bounds: $sampledRegion | " + TAG,
"screen width: ${displaySize.x}, screen height: ${displaySize.y}", "startRegionSampler() | view out of bounds: $screenLocationBounds | " +
Exception() "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
) Exception()
)
return return
} }
val regions = ArrayList<RectF>()
regions.add(sampledRegionWithOffset) regions.add(sampledRegionWithOffset)
wallpaperManager?.removeOnColorsChangedListener(this) wallpaperManager?.addOnColorsChangedListener(
wallpaperManager?.addOnColorsChangedListener(this, regions) this,
regions,
if (isLockscreen) WallpaperManager.FLAG_LOCK else WallpaperManager.FLAG_SYSTEM
)
// TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace
// implemented
bgExecutor?.execute( bgExecutor?.execute(
Runnable { Runnable {
val initialSampling = initialSampling =
wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK) wallpaperManager?.getWallpaperColors(
if (isLockscreen) WallpaperManager.FLAG_LOCK
else WallpaperManager.FLAG_SYSTEM
)
onColorsChanged(sampledRegionWithOffset, initialSampling) onColorsChanged(sampledRegionWithOffset, initialSampling)
} }
) )
@@ -126,6 +174,7 @@ constructor(
/** Stop region sampler */ /** Stop region sampler */
fun stopRegionSampler() { fun stopRegionSampler() {
wallpaperManager?.removeOnColorsChangedListener(this) wallpaperManager?.removeOnColorsChangedListener(this)
sampledView.removeOnLayoutChangeListener(layoutChangedListener)
} }
/** Dump region sampler */ /** Dump region sampler */
@@ -138,22 +187,23 @@ constructor(
pw.println("passed-in sampledView: $sampledView") pw.println("passed-in sampledView: $sampledView")
pw.println("calculated samplingBounds: $samplingBounds") pw.println("calculated samplingBounds: $samplingBounds")
pw.println( pw.println(
"sampledView width: ${sampledView?.width}, sampledView height: ${sampledView?.height}" "sampledView width: ${sampledView.width}, sampledView height: ${sampledView.height}"
) )
pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}") pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}")
pw.println( pw.println(
"sampledRegionWithOffset: ${convertBounds(calculateSampledRegion(sampledView!!))}" "sampledRegionWithOffset: ${convertBounds(
calculateScreenLocation(sampledView) ?: RectF())}"
) )
// TODO(b/265969235): mock initialSampling based on if component is on HS or LS wallpaper
// HS Smartspace - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
// LS Smartspace, clock - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
pw.println( pw.println(
"initialSampling for lockscreen: " + "initialSampling for ${if (isLockscreen) "lockscreen" else "homescreen" }" +
"${wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)}" ": $initialSampling"
) )
} }
fun calculateSampledRegion(sampledView: View): RectF { fun calculateScreenLocation(sampledView: View): RectF? {
if (!sampledView.isLaidOut) return null
val screenLocation = tmpScreenLocation val screenLocation = tmpScreenLocation
/** /**
* The method getLocationOnScreen is used to obtain the view coordinates relative to its * The method getLocationOnScreen is used to obtain the view coordinates relative to its
@@ -181,7 +231,8 @@ constructor(
*/ */
fun convertBounds(originalBounds: RectF): RectF { fun convertBounds(originalBounds: RectF): RectF {
// TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER (--> HS
// Smartspace always on 1st page)
// TODO(b/265968912): remove hard-coded value once LS wallpaper supported // TODO(b/265968912): remove hard-coded value once LS wallpaper supported
val wallpaperPageNum = 0 val wallpaperPageNum = 0
val numScreens = 1 val numScreens = 1
@@ -214,6 +265,11 @@ constructor(
) )
updateForegroundColor() updateForegroundColor()
} }
companion object {
private const val TAG = "RegionSampler"
private const val DEBUG = false
}
} }
typealias UpdateColorCallback = () -> Unit typealias UpdateColorCallback = () -> Unit

View File

@@ -21,13 +21,11 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Rect
import android.text.format.DateFormat import android.text.format.DateFormat
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.View.OnAttachStateChangeListener import android.view.View.OnAttachStateChangeListener
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
@@ -55,16 +53,16 @@ import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
/** /**
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -98,12 +96,7 @@ constructor(
value.initialize(resources, dozeAmount, 0f) value.initialize(resources, dozeAmount, 0f)
if (regionSamplingEnabled) { if (!regionSamplingEnabled) {
clock?.run {
smallClock.view.addOnLayoutChangeListener(mLayoutChangedListener)
largeClock.view.addOnLayoutChangeListener(mLayoutChangedListener)
}
} else {
updateColors() updateColors()
} }
updateFontSizes() updateFontSizes()
@@ -140,44 +133,10 @@ constructor(
private var disposableHandle: DisposableHandle? = null private var disposableHandle: DisposableHandle? = null
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
private val mLayoutChangedListener =
object : View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
view?.removeOnLayoutChangeListener(this)
val parent = (view?.parent) as FrameLayout
// don't pass in negative bounds when clocks are in transition state
if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) {
return
}
val currentViewRect = Rect(left, top, right, bottom)
val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
if (
currentViewRect.width() != oldViewRect.width() ||
currentViewRect.height() != oldViewRect.height()
) {
updateRegionSampler(view)
}
}
}
private fun updateColors() { private fun updateColors() {
val wallpaperManager = WallpaperManager.getInstance(context) val wallpaperManager = WallpaperManager.getInstance(context)
if (regionSamplingEnabled && !wallpaperManager.lockScreenWallpaperExists()) { if (regionSamplingEnabled) {
regionSampler?.let { regionSampler -> regionSampler?.let { regionSampler ->
clock?.let { clock -> clock?.let { clock ->
if (regionSampler.sampledView == clock.smallClock.view) { if (regionSampler.sampledView == clock.smallClock.view) {
@@ -212,6 +171,7 @@ constructor(
mainExecutor, mainExecutor,
bgExecutor, bgExecutor,
regionSamplingEnabled, regionSamplingEnabled,
isLockscreen = true,
::updateColors ::updateColors
) )
?.apply { startRegionSampler() } ?.apply { startRegionSampler() }
@@ -220,10 +180,11 @@ constructor(
} }
protected open fun createRegionSampler( protected open fun createRegionSampler(
sampledView: View?, sampledView: View,
mainExecutor: Executor?, mainExecutor: Executor?,
bgExecutor: Executor?, bgExecutor: Executor?,
regionSamplingEnabled: Boolean, regionSamplingEnabled: Boolean,
isLockscreen: Boolean,
updateColors: () -> Unit updateColors: () -> Unit
): RegionSampler? { ): RegionSampler? {
return RegionSampler( return RegionSampler(
@@ -231,8 +192,8 @@ constructor(
mainExecutor, mainExecutor,
bgExecutor, bgExecutor,
regionSamplingEnabled, regionSamplingEnabled,
updateColors isLockscreen,
) ) { updateColors() }
} }
var regionSampler: RegionSampler? = null var regionSampler: RegionSampler? = null

View File

@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.lockscreen package com.android.systemui.statusbar.lockscreen
import android.app.PendingIntent import android.app.PendingIntent
import android.app.WallpaperManager
import android.app.smartspace.SmartspaceConfig import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceSession
@@ -56,7 +55,6 @@ import com.android.systemui.plugins.WeatherData
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -120,7 +118,7 @@ constructor(
private val regionSamplingEnabled = private val regionSamplingEnabled =
featureFlags.isEnabled(Flags.REGION_SAMPLING) featureFlags.isEnabled(Flags.REGION_SAMPLING)
private var isContentUpdatedOnce = false private var isRegionSamplersCreated = false
private var showNotifications = false private var showNotifications = false
private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForCurrentUser = false
private var showSensitiveContentForManagedUser = false private var showSensitiveContentForManagedUser = false
@@ -128,7 +126,6 @@ constructor(
// TODO(b/202758428): refactor so that we can test color updates via region samping, similar to // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
// how we test color updates when theme changes (See testThemeChangeUpdatesTextColor). // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
// TODO: Move logic into SmartspaceView // TODO: Move logic into SmartspaceView
var stateChangeListener = object : View.OnAttachStateChangeListener { var stateChangeListener = object : View.OnAttachStateChangeListener {
@@ -144,6 +141,9 @@ constructor(
override fun onViewDetachedFromWindow(v: View) { override fun onViewDetachedFromWindow(v: View) {
smartspaceViews.remove(v as SmartspaceView) smartspaceViews.remove(v as SmartspaceView)
regionSamplers[v]?.stopRegionSampler()
regionSamplers.remove(v as SmartspaceView)
if (smartspaceViews.isEmpty()) { if (smartspaceViews.isEmpty()) {
disconnect() disconnect()
} }
@@ -170,7 +170,7 @@ constructor(
val filteredTargets = targets.filter(::filterSmartspaceTarget) val filteredTargets = targets.filter(::filterSmartspaceTarget)
plugin?.onTargetsAvailable(filteredTargets) plugin?.onTargetsAvailable(filteredTargets)
if (!isContentUpdatedOnce) { if (!isRegionSamplersCreated) {
for (v in smartspaceViews) { for (v in smartspaceViews) {
if (regionSamplingEnabled) { if (regionSamplingEnabled) {
var regionSampler = RegionSampler( var regionSampler = RegionSampler(
@@ -178,15 +178,14 @@ constructor(
uiExecutor, uiExecutor,
bgExecutor, bgExecutor,
regionSamplingEnabled, regionSamplingEnabled,
updateFun isLockscreen = true,
) ) { updateTextColorFromRegionSampler() }
initializeTextColors(regionSampler) initializeTextColors(regionSampler)
regionSamplers[v] = regionSampler regionSamplers[v] = regionSampler
regionSampler.startRegionSampler() regionSampler.startRegionSampler()
} }
updateTextColorFromWallpaper()
} }
isContentUpdatedOnce = true isRegionSamplersCreated = true
} }
} }
@@ -504,18 +503,16 @@ constructor(
} }
private fun updateTextColorFromRegionSampler() { private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach { regionSamplers.forEach { (view, region) ->
val textColor = regionSamplers.get(it)?.currentForegroundColor() val textColor = region.currentForegroundColor()
if (textColor != null) { if (textColor != null) {
it.setPrimaryTextColor(textColor) view.setPrimaryTextColor(textColor)
} }
} }
} }
private fun updateTextColorFromWallpaper() { private fun updateTextColorFromWallpaper() {
val wallpaperManager = WallpaperManager.getInstance(context) if (!regionSamplingEnabled || regionSamplers.isEmpty()) {
if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists() ||
regionSamplers.isEmpty()) {
val wallpaperTextColor = val wallpaperTextColor =
Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor) Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) } smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }

View File

@@ -1,17 +1,30 @@
package com.android.systemui.shared.regionsampling package com.android.systemui.shared.regionsampling
import android.app.WallpaperColors
import android.app.WallpaperManager import android.app.WallpaperManager
import android.graphics.Color
import android.graphics.RectF
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.view.View import android.view.View
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter import java.io.PrintWriter
import java.util.concurrent.Executor import java.util.concurrent.Executor
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnit
@@ -26,25 +39,188 @@ class RegionSamplerTest : SysuiTestCase() {
@Mock private lateinit var bgExecutor: Executor @Mock private lateinit var bgExecutor: Executor
@Mock private lateinit var pw: PrintWriter @Mock private lateinit var pw: PrintWriter
@Mock private lateinit var wallpaperManager: WallpaperManager @Mock private lateinit var wallpaperManager: WallpaperManager
@Mock private lateinit var updateForegroundColor: UpdateColorCallback
private lateinit var mRegionSampler: RegionSampler private lateinit var regionSampler: RegionSampler // lockscreen
private var updateFun: UpdateColorCallback = {} private lateinit var homescreenRegionSampler: RegionSampler
@Captor
private lateinit var colorsChangedListener:
ArgumentCaptor<WallpaperManager.LocalWallpaperColorConsumer>
@Captor private lateinit var layoutChangedListener: ArgumentCaptor<View.OnLayoutChangeListener>
@Before @Before
fun setUp() { fun setUp() {
whenever(sampledView.isAttachedToWindow).thenReturn(true) whenever(sampledView.isAttachedToWindow).thenReturn(true)
whenever(sampledView.width).thenReturn(100)
whenever(sampledView.height).thenReturn(100)
whenever(sampledView.isLaidOut).thenReturn(true)
whenever(sampledView.locationOnScreen).thenReturn(intArrayOf(0, 0))
mRegionSampler = regionSampler =
RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun, wallpaperManager) RegionSampler(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled = true,
isLockscreen = true,
wallpaperManager,
updateForegroundColor
)
regionSampler.displaySize.set(1080, 2050)
// TODO(b/265969235): test sampling on home screen via WallpaperManager.FLAG_SYSTEM
homescreenRegionSampler =
RegionSampler(
sampledView,
mainExecutor,
bgExecutor,
regionSamplingEnabled = true,
isLockscreen = false,
wallpaperManager,
updateForegroundColor
)
} }
@Test @Test
fun testStartRegionSampler() { fun testCalculatedBounds_inRange() {
mRegionSampler.startRegionSampler() // test calculations return region within [0,1]
sampledView.setLeftTopRightBottom(100, 100, 200, 200)
var fractionalBounds =
regionSampler.calculateScreenLocation(sampledView)?.let {
regionSampler.convertBounds(it)
}
assertTrue(fractionalBounds?.left!! >= 0.0f)
assertTrue(fractionalBounds.right <= 1.0f)
assertTrue(fractionalBounds.top >= 0.0f)
assertTrue(fractionalBounds.bottom <= 1.0f)
}
@Test
fun testEmptyView_returnsEarly() {
sampledView.setLeftTopRightBottom(0, 0, 0, 0)
whenever(sampledView.width).thenReturn(0)
whenever(sampledView.height).thenReturn(0)
regionSampler.startRegionSampler()
// returns early so should never call this function
verify(wallpaperManager, never())
.addOnColorsChangedListener(
any(WallpaperManager.LocalWallpaperColorConsumer::class.java),
any(),
any()
)
}
@Test
fun testLayoutChange_notifiesListener() {
regionSampler.startRegionSampler()
// don't count addOnColorsChangedListener() call made in startRegionSampler()
clearInvocations(wallpaperManager)
verify(sampledView).addOnLayoutChangeListener(capture(layoutChangedListener))
layoutChangedListener.value.onLayoutChange(
sampledView,
300,
300,
400,
400,
100,
100,
200,
200
)
verify(sampledView).removeOnLayoutChangeListener(layoutChangedListener.value)
verify(wallpaperManager)
.removeOnColorsChangedListener(
any(WallpaperManager.LocalWallpaperColorConsumer::class.java)
)
verify(wallpaperManager)
.addOnColorsChangedListener(
any(WallpaperManager.LocalWallpaperColorConsumer::class.java),
any(),
any()
)
}
@Test
fun testColorsChanged_triggersCallback() {
regionSampler.startRegionSampler()
verify(wallpaperManager)
.addOnColorsChangedListener(
capture(colorsChangedListener),
any(),
eq(WallpaperManager.FLAG_LOCK)
)
setWhiteWallpaper()
verify(updateForegroundColor).invoke()
}
@Test
fun testRegionDarkness() {
regionSampler.startRegionSampler()
verify(wallpaperManager)
.addOnColorsChangedListener(
capture(colorsChangedListener),
any(),
eq(WallpaperManager.FLAG_LOCK)
)
// should detect dark region
setBlackWallpaper()
assertThat(regionSampler.currentRegionDarkness()).isEqualTo(RegionDarkness.DARK)
// should detect light region
setWhiteWallpaper()
assertThat(regionSampler.currentRegionDarkness()).isEqualTo(RegionDarkness.LIGHT)
}
@Test
fun testForegroundColor() {
regionSampler.setForegroundColors(Color.WHITE, Color.BLACK)
regionSampler.startRegionSampler()
verify(wallpaperManager)
.addOnColorsChangedListener(
capture(colorsChangedListener),
any(),
eq(WallpaperManager.FLAG_LOCK)
)
// dark background, light text
setBlackWallpaper()
assertThat(regionSampler.currentForegroundColor()).isEqualTo(Color.WHITE)
// light background, dark text
setWhiteWallpaper()
assertThat(regionSampler.currentForegroundColor()).isEqualTo(Color.BLACK)
}
private fun setBlackWallpaper() {
val wallpaperColors =
WallpaperColors(Color.valueOf(Color.BLACK), Color.valueOf(Color.BLACK), null)
colorsChangedListener.value.onColorsChanged(
RectF(100.0f, 100.0f, 200.0f, 200.0f),
wallpaperColors
)
}
private fun setWhiteWallpaper() {
val wallpaperColors =
WallpaperColors(
Color.valueOf(Color.WHITE),
Color.valueOf(Color.WHITE),
null,
WallpaperColors.HINT_SUPPORTS_DARK_TEXT
)
colorsChangedListener.value.onColorsChanged(
RectF(100.0f, 100.0f, 200.0f, 200.0f),
wallpaperColors
)
} }
@Test @Test
fun testDump() { fun testDump() {
mRegionSampler.dump(pw) regionSampler.dump(pw)
homescreenRegionSampler.dump(pw)
} }
} }

View File

@@ -48,10 +48,10 @@ public class WallpaperColorsTest {
} }
/** /**
* Check that white supports dark text and black doesn't * Check that white surface supports dark text
*/ */
@Test @Test
public void colorHintsTest() { public void whiteSurfaceColorHintsTest() {
Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888); Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image); Canvas canvas = new Canvas(image);
@@ -59,28 +59,68 @@ public class WallpaperColorsTest {
int hints = WallpaperColors.fromBitmap(image).getColorHints(); int hints = WallpaperColors.fromBitmap(image).getColorHints();
boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0; boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
boolean fromBitmap = (hints & WallpaperColors.HINT_FROM_BITMAP) != 0;
Assert.assertTrue("White surface should support dark text.", supportsDarkText); Assert.assertTrue("White surface should support dark text.", supportsDarkText);
Assert.assertFalse("White surface shouldn't support dark theme.", supportsDarkTheme); Assert.assertFalse("White surface shouldn't support dark theme.", supportsDarkTheme);
Assert.assertTrue("From bitmap should be true if object was created "
+ "using WallpaperColors#fromBitmap.", fromBitmap);
canvas.drawColor(Color.BLACK);
hints = WallpaperColors.fromBitmap(image).getColorHints();
supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
Assert.assertFalse("Black surface shouldn't support dark text.", supportsDarkText);
Assert.assertTrue("Black surface should support dark theme.", supportsDarkTheme);
Paint paint = new Paint(); Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL); paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK); paint.setColor(Color.BLACK);
canvas.drawColor(Color.WHITE);
canvas.drawRect(0, 0, 8, 8, paint); canvas.drawRect(0, 0, 8, 8, paint);
supportsDarkText = (WallpaperColors.fromBitmap(image) supportsDarkText = (WallpaperColors.fromBitmap(image)
.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0; .getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
Assert.assertFalse("Light surface shouldn't support dark text " Assert.assertFalse("Light surface shouldn't support dark text "
+ "when it contains dark pixels.", supportsDarkText); + "when it contains dark pixels.", supportsDarkText);
}
/**
* Check that x-small white region supports dark text when max number of dark pixels = 0
*/
@Test
public void xSmallWhiteSurfaceColorHintsTest() {
Bitmap xsmall_image = Bitmap.createBitmap(1, 5, Bitmap.Config.ARGB_8888);
Canvas xsmall_canvas = new Canvas(xsmall_image);
xsmall_canvas.drawColor(Color.WHITE);
int hints = WallpaperColors.fromBitmap(xsmall_image).getColorHints();
boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
Assert.assertTrue("X-small white surface should support dark text.",
supportsDarkText);
Assert.assertFalse("X-small white surface shouldn't support dark theme.",
supportsDarkTheme);
}
/**
* Check that black surface doesn't support dark text
*/
@Test
public void blackSurfaceColorHintsTest() {
Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image);
canvas.drawColor(Color.BLACK);
int hints = WallpaperColors.fromBitmap(image).getColorHints();
boolean supportsDarkText = (hints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0;
boolean supportsDarkTheme = (hints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0;
Assert.assertFalse("Black surface shouldn't support dark text.", supportsDarkText);
Assert.assertTrue("Black surface should support dark theme.", supportsDarkTheme);
}
/**
* Check that bitmap hint properly indicates when object created via WallpaperColors#fromBitmap
* versus WallpaperColors() public constructor
*/
@Test
public void bitmapHintsTest() {
Bitmap image = Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(image);
canvas.drawColor(Color.WHITE);
int hints = WallpaperColors.fromBitmap(image).getColorHints();
boolean fromBitmap = (hints & WallpaperColors.HINT_FROM_BITMAP) != 0;
Assert.assertTrue("From bitmap should be true if object was created "
+ "using WallpaperColors#fromBitmap.", fromBitmap);
WallpaperColors colors = new WallpaperColors(Color.valueOf(Color.GREEN), null, null); WallpaperColors colors = new WallpaperColors(Color.valueOf(Color.GREEN), null, null);
fromBitmap = (colors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) != 0; fromBitmap = (colors.getColorHints() & WallpaperColors.HINT_FROM_BITMAP) != 0;