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

Commit a7786efb authored by Florence Yang's avatar Florence Yang Committed by Automerger Merge Worker
Browse files

Merge "Region Sampler Wallpaper Colors Implementation" into tm-qpr-dev am: 86be4a9d

parents c033d904 86be4a9d
Loading
Loading
Loading
Loading
+96 −56
Original line number Diff line number Diff line
@@ -15,39 +15,36 @@
 */
package com.android.systemui.shared.regionsampling

import android.app.WallpaperColors
import android.app.WallpaperManager
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import android.graphics.RectF
import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.systemui.shared.navigationbar.RegionSamplingHelper
import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCallback
import java.io.PrintWriter
import java.util.concurrent.Executor

/** Class for instance of RegionSamplingHelper */
open class RegionSampler(
    sampledView: View?,
open class RegionSampler
@JvmOverloads
constructor(
    val sampledView: View?,
    mainExecutor: Executor?,
    bgExecutor: Executor?,
    regionSamplingEnabled: Boolean,
    updateFun: UpdateColorCallback
) {
    val bgExecutor: Executor?,
    val regionSamplingEnabled: Boolean,
    val updateForegroundColor: UpdateColorCallback,
    val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context)
) : WallpaperManager.LocalWallpaperColorConsumer {
    private var regionDarkness = RegionDarkness.DEFAULT
    private var samplingBounds = Rect()
    private val tmpScreenLocation = IntArray(2)
    @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
    private var lightForegroundColor = Color.WHITE
    private var darkForegroundColor = Color.BLACK

    @VisibleForTesting
    open fun createRegionSamplingHelper(
        sampledView: View,
        callback: SamplingCallback,
        mainExecutor: Executor?,
        bgExecutor: Executor?
    ): RegionSamplingHelper {
        return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
    }
    private val displaySize = Point()

    /**
     * Sets the colors to be used for Dark and Light Foreground.
@@ -73,7 +70,7 @@ open class RegionSampler(
        }
    }

    private fun convertToClockDarkness(isRegionDark: Boolean): RegionDarkness {
    private fun getRegionDarkness(isRegionDark: Boolean): RegionDarkness {
        return if (isRegionDark) {
            RegionDarkness.DARK
        } else {
@@ -87,12 +84,32 @@ open class RegionSampler(

    /** Start region sampler */
    fun startRegionSampler() {
        regionSampler?.start(samplingBounds)
        if (!regionSamplingEnabled || sampledView == null) {
            return
        }

        val sampledRegion = calculateSampledRegion(sampledView)
        val regions = ArrayList<RectF>()
        val sampledRegionWithOffset = convertBounds(sampledRegion)
        regions.add(sampledRegionWithOffset)

        wallpaperManager?.removeOnColorsChangedListener(this)
        wallpaperManager?.addOnColorsChangedListener(this, regions)

        // TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace
        // implemented
        bgExecutor?.execute(
            Runnable {
                val initialSampling =
                    wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
                onColorsChanged(sampledRegionWithOffset, initialSampling)
            }
        )
    }

    /** Stop region sampler */
    fun stopRegionSampler() {
        regionSampler?.stop()
        wallpaperManager?.removeOnColorsChangedListener(this)
    }

    /** Dump region sampler */
@@ -100,43 +117,66 @@ open class RegionSampler(
        regionSampler?.dump(pw)
    }

    init {
        if (regionSamplingEnabled && sampledView != null) {
            regionSampler =
                createRegionSamplingHelper(
                    sampledView,
                    object : SamplingCallback {
                        override fun onRegionDarknessChanged(isRegionDark: Boolean) {
                            regionDarkness = convertToClockDarkness(isRegionDark)
                            updateFun()
                        }
    fun calculateSampledRegion(sampledView: View): RectF {
        val screenLocation = tmpScreenLocation
        /**
                         * The method getLocationOnScreen is used to obtain the view coordinates
                         * relative to its left and top edges on the device screen. Directly
                         * accessing the X and Y coordinates of the view returns the location
                         * relative to its parent view instead.
         * The method getLocationOnScreen is used to obtain the view coordinates relative to its
         * left and top edges on the device screen. Directly accessing the X and Y coordinates of
         * the view returns the location relative to its parent view instead.
         */
                        override fun getSampledRegion(sampledView: View): Rect {
                            val screenLocation = tmpScreenLocation
        sampledView.getLocationOnScreen(screenLocation)
        val left = screenLocation[0]
        val top = screenLocation[1]

        samplingBounds.left = left
        samplingBounds.top = top
        samplingBounds.right = left + sampledView.width
        samplingBounds.bottom = top + sampledView.height
                            return samplingBounds

        return RectF(samplingBounds)
    }

                        override fun isSamplingEnabled(): Boolean {
                            return regionSamplingEnabled
    /**
     * Convert the bounds of the region we want to sample from to fractional offsets because
     * WallpaperManager requires the bounds to be between [0,1]. The wallpaper is treated as one
     * continuous image, so if there are multiple screens, then each screen falls into a fractional
     * range. For instance, 4 screens have the ranges [0, 0.25], [0,25, 0.5], [0.5, 0.75], [0.75,
     * 1].
     */
    fun convertBounds(originalBounds: RectF): RectF {

        // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER
        // TODO(b/265968912): remove hard-coded value once LS wallpaper supported
        val wallpaperPageNum = 0
        val numScreens = 1

        val screenWidth = displaySize.x
        // TODO: investigate small difference between this and the height reported in go/web-hv
        val screenHeight = displaySize.y

        val newBounds = RectF()
        // horizontal
        newBounds.left = ((originalBounds.left / screenWidth) + wallpaperPageNum) / numScreens
        newBounds.right = ((originalBounds.right / screenWidth) + wallpaperPageNum) / numScreens
        // vertical
        newBounds.top = originalBounds.top / screenHeight
        newBounds.bottom = originalBounds.bottom / screenHeight

        return newBounds
    }
                    },
                    mainExecutor,
                    bgExecutor
                )

    init {
        sampledView?.context?.display?.getSize(displaySize)
    }
        regionSampler?.setWindowVisible(true)

    override fun onColorsChanged(area: RectF?, colors: WallpaperColors?) {
        // update text color when wallpaper color changes
        regionDarkness =
            getRegionDarkness(
                (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
                    WallpaperColors.HINT_SUPPORTS_DARK_TEXT
            )
        updateForegroundColor()
    }
}

+88 −49
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.res.Resources
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
@@ -47,18 +48,17 @@ import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
import com.android.systemui.statusbar.policy.ConfigurationController
import java.io.PrintWriter
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import java.io.PrintWriter
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
@@ -89,7 +89,11 @@ open class ClockEventController @Inject constructor(
                value.largeClock.logBuffer = largeLogBuffer

                value.initialize(resources, dozeAmount, 0f)
                updateRegionSamplers(value)

                if (regionSamplingEnabled) {
                    clock?.smallClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
                    clock?.largeClock?.view?.addOnLayoutChangeListener(mLayoutChangedListener)
                }
                updateFontSizes()
            }
        }
@@ -104,47 +108,87 @@ open class ClockEventController @Inject constructor(
    private var disposableHandle: DisposableHandle? = null
    private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)

    private fun updateColors() {
    private val mLayoutChangedListener = object : View.OnLayoutChangeListener {
        private var currentSmallClockView: View? = null
        private var currentLargeClockView: View? = null
        private var currentSmallClockLocation = IntArray(2)
        private var currentLargeClockLocation = IntArray(2)

        override fun onLayoutChange(
            view: View?,
            left: Int,
            top: Int,
            right: Int,
            bottom: Int,
            oldLeft: Int,
            oldTop: Int,
            oldRight: Int,
            oldBottom: Int
        ) {
        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
        }

        if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
        // SMALL CLOCK
        if (parent.id == R.id.lockscreen_clock_view) {
            // view bounds have changed due to clock size changing (i.e. different character widths)
            // AND/OR the view has been translated when transitioning between small and large clock
            if (view != currentSmallClockView ||
                !view.locationOnScreen.contentEquals(currentSmallClockLocation)) {
                currentSmallClockView = view
                currentSmallClockLocation = view.locationOnScreen
                updateRegionSampler(view)
            }
        }
        // LARGE CLOCK
        else if (parent.id == R.id.lockscreen_clock_view_large) {
            if (view != currentLargeClockView ||
                !view.locationOnScreen.contentEquals(currentLargeClockLocation)) {
                currentLargeClockView = view
                currentLargeClockLocation = view.locationOnScreen
                updateRegionSampler(view)
            }
        }
        }
    }

    private fun updateColors() {
        val wallpaperManager = WallpaperManager.getInstance(context)
            if (!wallpaperManager.lockScreenWallpaperExists()) {
                smallClockIsDark = smallRegionSampler!!.currentRegionDarkness().isDark
                largeClockIsDark = largeRegionSampler!!.currentRegionDarkness().isDark
        if (regionSamplingEnabled && !wallpaperManager.lockScreenWallpaperExists()) {
            if (regionSampler != null) {
                if (regionSampler?.sampledView == clock?.smallClock?.view) {
                    smallClockIsDark = regionSampler!!.currentRegionDarkness().isDark
                    clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
                    return
                } else if (regionSampler?.sampledView == clock?.largeClock?.view) {
                    largeClockIsDark = regionSampler!!.currentRegionDarkness().isDark
                    clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
                    return
                }
        } else {
            }
        }

        val isLightTheme = TypedValue()
        context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
        smallClockIsDark = isLightTheme.data == 0
        largeClockIsDark = isLightTheme.data == 0
        }

        clock?.smallClock?.events?.onRegionDarknessChanged(smallClockIsDark)
        clock?.largeClock?.events?.onRegionDarknessChanged(largeClockIsDark)
    }

    private fun updateRegionSamplers(currentClock: ClockController?) {
        smallRegionSampler?.stopRegionSampler()
        largeRegionSampler?.stopRegionSampler()

        smallRegionSampler = createRegionSampler(
                currentClock?.smallClock?.view,
                mainExecutor,
                bgExecutor,
                regionSamplingEnabled,
                ::updateColors
        )

        largeRegionSampler = createRegionSampler(
                currentClock?.largeClock?.view,
    private fun updateRegionSampler(sampledRegion: View) {
        regionSampler?.stopRegionSampler()
        regionSampler = createRegionSampler(
            sampledRegion,
            mainExecutor,
            bgExecutor,
            regionSamplingEnabled,
            ::updateColors
        )

        smallRegionSampler!!.startRegionSampler()
        largeRegionSampler!!.startRegionSampler()
        )?.apply { startRegionSampler() }

        updateColors()
    }
@@ -155,7 +199,7 @@ open class ClockEventController @Inject constructor(
            bgExecutor: Executor?,
            regionSamplingEnabled: Boolean,
            updateColors: () -> Unit
    ): RegionSampler {
    ): RegionSampler? {
        return RegionSampler(
            sampledView,
            mainExecutor,
@@ -164,8 +208,7 @@ open class ClockEventController @Inject constructor(
            updateColors)
    }

    var smallRegionSampler: RegionSampler? = null
    var largeRegionSampler: RegionSampler? = null
    var regionSampler: RegionSampler? = null

    private var smallClockIsDark = true
    private var largeClockIsDark = true
@@ -232,8 +275,6 @@ open class ClockEventController @Inject constructor(
        configurationController.addCallback(configListener)
        batteryController.addCallback(batteryCallback)
        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
        smallRegionSampler?.startRegionSampler()
        largeRegionSampler?.startRegionSampler()
        disposableHandle = parent.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                listenForDozing(this)
@@ -258,8 +299,7 @@ open class ClockEventController @Inject constructor(
        configurationController.removeCallback(configListener)
        batteryController.removeCallback(batteryCallback)
        keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
        smallRegionSampler?.stopRegionSampler()
        largeRegionSampler?.stopRegionSampler()
        regionSampler?.stopRegionSampler()
    }

    private fun updateFontSizes() {
@@ -275,8 +315,7 @@ open class ClockEventController @Inject constructor(
    fun dump(pw: PrintWriter) {
        pw.println(this)
        clock?.dump(pw)
        smallRegionSampler?.dump(pw)
        largeRegionSampler?.dump(pw)
        regionSampler?.dump(pw)
    }

    @VisibleForTesting
+21 −21
Original line number Diff line number Diff line
@@ -100,7 +100,7 @@ constructor(

    private val regionSamplingEnabled =
            featureFlags.isEnabled(Flags.REGION_SAMPLING)

    private var isContentUpdatedOnce = false
    private var showNotifications = false
    private var showSensitiveContentForCurrentUser = false
    private var showSensitiveContentForManagedUser = false
@@ -114,19 +114,6 @@ constructor(
        override fun onViewAttachedToWindow(v: View) {
            smartspaceViews.add(v as SmartspaceView)

            if (regionSamplingEnabled) {
                var regionSampler = RegionSampler(
                        v,
                        uiExecutor,
                        bgExecutor,
                        regionSamplingEnabled,
                        updateFun
                )
                initializeTextColors(regionSampler)
                regionSampler.startRegionSampler()
                regionSamplers.put(v, regionSampler)
            }

            connectSession()

            updateTextColorFromWallpaper()
@@ -136,12 +123,6 @@ constructor(
        override fun onViewDetachedFromWindow(v: View) {
            smartspaceViews.remove(v as SmartspaceView)

            if (regionSamplingEnabled) {
                var regionSampler = regionSamplers.getValue(v)
                regionSampler.stopRegionSampler()
                regionSamplers.remove(v)
            }

            if (smartspaceViews.isEmpty()) {
                disconnect()
            }
@@ -152,6 +133,24 @@ constructor(
        execution.assertIsMainThread()
        val filteredTargets = targets.filter(::filterSmartspaceTarget)
        plugin?.onTargetsAvailable(filteredTargets)
        if (!isContentUpdatedOnce) {
            for (v in smartspaceViews) {
                if (regionSamplingEnabled) {
                    var regionSampler = RegionSampler(
                        v as View,
                        uiExecutor,
                        bgExecutor,
                        regionSamplingEnabled,
                        updateFun
                    )
                    initializeTextColors(regionSampler)
                    regionSamplers[v] = regionSampler
                    regionSampler.startRegionSampler()
                }
                updateTextColorFromWallpaper()
            }
            isContentUpdatedOnce = true
        }
    }

    private val userTrackerCallback = object : UserTracker.Callback {
@@ -398,7 +397,8 @@ constructor(

    private fun updateTextColorFromWallpaper() {
        val wallpaperManager = WallpaperManager.getInstance(context)
        if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists()) {
        if (!regionSamplingEnabled || wallpaperManager.lockScreenWallpaperExists() ||
            regionSamplers.isEmpty()) {
            val wallpaperTextColor =
                    Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
            smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
+3 −6
Original line number Diff line number Diff line
@@ -51,7 +51,6 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -140,8 +139,9 @@ class ClockEventControllerTest : SysuiTestCase() {

    @Test
    fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
        verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
        verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
        // TODO(b/266103601): delete this test and add more coverage for updateColors()
        // verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
        // verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())

        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
        verify(configurationController).addCallback(capture(captor))
@@ -152,9 +152,6 @@ class ClockEventControllerTest : SysuiTestCase() {

    @Test
    fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
        verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
        verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())

        val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
        verify(configurationController).addCallback(capture(captor))
        captor.value.onDensityOrFontScaleChanged()
+3 −55
Original line number Diff line number Diff line
package com.android.systemui.shared.regionsampling

import android.graphics.Rect
import android.app.WallpaperManager
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.navigationbar.RegionSamplingHelper
import java.io.PrintWriter
import java.util.concurrent.Executor
import org.junit.Assert
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit

@@ -28,9 +24,8 @@ class RegionSamplerTest : SysuiTestCase() {
    @Mock private lateinit var sampledView: View
    @Mock private lateinit var mainExecutor: Executor
    @Mock private lateinit var bgExecutor: Executor
    @Mock private lateinit var regionSampler: RegionSamplingHelper
    @Mock private lateinit var pw: PrintWriter
    @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
    @Mock private lateinit var wallpaperManager: WallpaperManager

    private lateinit var mRegionSampler: RegionSampler
    private var updateFun: UpdateColorCallback = {}
@@ -38,65 +33,18 @@ class RegionSamplerTest : SysuiTestCase() {
    @Before
    fun setUp() {
        whenever(sampledView.isAttachedToWindow).thenReturn(true)
        whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)

        mRegionSampler =
            object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
                override fun createRegionSamplingHelper(
                    sampledView: View,
                    callback: RegionSamplingHelper.SamplingCallback,
                    mainExecutor: Executor?,
                    bgExecutor: Executor?
                ): RegionSamplingHelper {
                    return this@RegionSamplerTest.regionSampler
                }
            }
            RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun, wallpaperManager)
    }

    @Test
    fun testStartRegionSampler() {
        mRegionSampler.startRegionSampler()

        verify(regionSampler).start(Rect(0, 0, 0, 0))
    }

    @Test
    fun testStopRegionSampler() {
        mRegionSampler.stopRegionSampler()

        verify(regionSampler).stop()
    }

    @Test
    fun testDump() {
        mRegionSampler.dump(pw)

        verify(regionSampler).dump(pw)
    }

    @Test
    fun testUpdateColorCallback() {
        regionSampler.callback.onRegionDarknessChanged(false)
        verify(regionSampler.callback).onRegionDarknessChanged(false)
        clearInvocations(regionSampler.callback)
        regionSampler.callback.onRegionDarknessChanged(true)
        verify(regionSampler.callback).onRegionDarknessChanged(true)
    }

    @Test
    fun testFlagFalse() {
        mRegionSampler =
            object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
                override fun createRegionSamplingHelper(
                    sampledView: View,
                    callback: RegionSamplingHelper.SamplingCallback,
                    mainExecutor: Executor?,
                    bgExecutor: Executor?
                ): RegionSamplingHelper {
                    return this@RegionSamplerTest.regionSampler
                }
            }

        Assert.assertEquals(mRegionSampler.regionSampler, null)
    }
}