• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.systemui.shared.regionsampling
17 
18 import android.app.WallpaperColors
19 import android.app.WallpaperManager
20 import android.graphics.Color
21 import android.graphics.Point
22 import android.graphics.Rect
23 import android.graphics.RectF
24 import android.view.View
25 import androidx.annotation.VisibleForTesting
26 import com.android.systemui.shared.navigationbar.RegionSamplingHelper
27 import java.io.PrintWriter
28 import java.util.concurrent.Executor
29 
30 /** Class for instance of RegionSamplingHelper */
31 open class RegionSampler
32 @JvmOverloads
33 constructor(
34     val sampledView: View?,
35     mainExecutor: Executor?,
36     val bgExecutor: Executor?,
37     val regionSamplingEnabled: Boolean,
38     val updateForegroundColor: UpdateColorCallback,
39     val wallpaperManager: WallpaperManager? = WallpaperManager.getInstance(sampledView?.context)
40 ) : WallpaperManager.LocalWallpaperColorConsumer {
41     private var regionDarkness = RegionDarkness.DEFAULT
42     private var samplingBounds = Rect()
43     private val tmpScreenLocation = IntArray(2)
44     @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
45     private var lightForegroundColor = Color.WHITE
46     private var darkForegroundColor = Color.BLACK
47     private val displaySize = Point()
48 
49     /**
50      * Sets the colors to be used for Dark and Light Foreground.
51      *
52      * @param lightColor The color used for Light Foreground.
53      * @param darkColor The color used for Dark Foreground.
54      */
setForegroundColorsnull55     fun setForegroundColors(lightColor: Int, darkColor: Int) {
56         lightForegroundColor = lightColor
57         darkForegroundColor = darkColor
58     }
59 
60     /**
61      * Determines which foreground color to use based on region darkness.
62      *
63      * @return the determined foreground color
64      */
currentForegroundColornull65     fun currentForegroundColor(): Int {
66         return if (regionDarkness.isDark) {
67             lightForegroundColor
68         } else {
69             darkForegroundColor
70         }
71     }
72 
getRegionDarknessnull73     private fun getRegionDarkness(isRegionDark: Boolean): RegionDarkness {
74         return if (isRegionDark) {
75             RegionDarkness.DARK
76         } else {
77             RegionDarkness.LIGHT
78         }
79     }
80 
currentRegionDarknessnull81     fun currentRegionDarkness(): RegionDarkness {
82         return regionDarkness
83     }
84 
85     /** Start region sampler */
startRegionSamplernull86     fun startRegionSampler() {
87         if (!regionSamplingEnabled || sampledView == null) {
88             return
89         }
90 
91         val sampledRegion = calculateSampledRegion(sampledView)
92         val regions = ArrayList<RectF>()
93         val sampledRegionWithOffset = convertBounds(sampledRegion)
94 
95         if (
96             sampledRegionWithOffset.left < 0.0 ||
97                 sampledRegionWithOffset.right > 1.0 ||
98                 sampledRegionWithOffset.top < 0.0 ||
99                 sampledRegionWithOffset.bottom > 1.0
100         ) {
101             android.util.Log.e(
102                 "RegionSampler",
103                 "view out of bounds: $sampledRegion | " +
104                     "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
105                 Exception()
106             )
107             return
108         }
109 
110         regions.add(sampledRegionWithOffset)
111 
112         wallpaperManager?.removeOnColorsChangedListener(this)
113         wallpaperManager?.addOnColorsChangedListener(this, regions)
114 
115         // TODO(b/265969235): conditionally set FLAG_LOCK or FLAG_SYSTEM once HS smartspace
116         // implemented
117         bgExecutor?.execute(
118             Runnable {
119                 val initialSampling =
120                     wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
121                 onColorsChanged(sampledRegionWithOffset, initialSampling)
122             }
123         )
124     }
125 
126     /** Stop region sampler */
stopRegionSamplernull127     fun stopRegionSampler() {
128         wallpaperManager?.removeOnColorsChangedListener(this)
129     }
130 
131     /** Dump region sampler */
dumpnull132     fun dump(pw: PrintWriter) {
133         pw.println("[RegionSampler]")
134         pw.println("regionSamplingEnabled: $regionSamplingEnabled")
135         pw.println("regionDarkness: $regionDarkness")
136         pw.println("lightForegroundColor: ${Integer.toHexString(lightForegroundColor)}")
137         pw.println("darkForegroundColor: ${Integer.toHexString(darkForegroundColor)}")
138         pw.println("passed-in sampledView: $sampledView")
139         pw.println("calculated samplingBounds: $samplingBounds")
140         pw.println(
141             "sampledView width: ${sampledView?.width}, sampledView height: ${sampledView?.height}"
142         )
143         pw.println("screen width: ${displaySize.x}, screen height: ${displaySize.y}")
144         pw.println(
145             "sampledRegionWithOffset: ${convertBounds(calculateSampledRegion(sampledView!!))}"
146         )
147         // TODO(b/265969235): mock initialSampling based on if component is on HS or LS wallpaper
148         // HS Smartspace - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_SYSTEM)
149         // LS Smartspace, clock - wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)
150         pw.println(
151             "initialSampling for lockscreen: " +
152                 "${wallpaperManager?.getWallpaperColors(WallpaperManager.FLAG_LOCK)}"
153         )
154     }
155 
calculateSampledRegionnull156     fun calculateSampledRegion(sampledView: View): RectF {
157         val screenLocation = tmpScreenLocation
158         /**
159          * The method getLocationOnScreen is used to obtain the view coordinates relative to its
160          * left and top edges on the device screen. Directly accessing the X and Y coordinates of
161          * the view returns the location relative to its parent view instead.
162          */
163         sampledView.getLocationOnScreen(screenLocation)
164         val left = screenLocation[0]
165         val top = screenLocation[1]
166 
167         samplingBounds.left = left
168         samplingBounds.top = top
169         samplingBounds.right = left + sampledView.width
170         samplingBounds.bottom = top + sampledView.height
171 
172         return RectF(samplingBounds)
173     }
174 
175     /**
176      * Convert the bounds of the region we want to sample from to fractional offsets because
177      * WallpaperManager requires the bounds to be between [0,1]. The wallpaper is treated as one
178      * continuous image, so if there are multiple screens, then each screen falls into a fractional
179      * range. For instance, 4 screens have the ranges [0, 0.25], [0,25, 0.5], [0.5, 0.75], [0.75,
180      * 1].
181      */
convertBoundsnull182     fun convertBounds(originalBounds: RectF): RectF {
183 
184         // TODO(b/265969235): GRAB # PAGES + CURRENT WALLPAPER PAGE # FROM LAUNCHER
185         // TODO(b/265968912): remove hard-coded value once LS wallpaper supported
186         val wallpaperPageNum = 0
187         val numScreens = 1
188 
189         val screenWidth = displaySize.x
190         // TODO: investigate small difference between this and the height reported in go/web-hv
191         val screenHeight = displaySize.y
192 
193         val newBounds = RectF()
194         // horizontal
195         newBounds.left = ((originalBounds.left / screenWidth) + wallpaperPageNum) / numScreens
196         newBounds.right = ((originalBounds.right / screenWidth) + wallpaperPageNum) / numScreens
197         // vertical
198         newBounds.top = originalBounds.top / screenHeight
199         newBounds.bottom = originalBounds.bottom / screenHeight
200 
201         return newBounds
202     }
203 
204     init {
205         sampledView?.context?.display?.getSize(displaySize)
206     }
207 
onColorsChangednull208     override fun onColorsChanged(area: RectF?, colors: WallpaperColors?) {
209         // update text color when wallpaper color changes
210         regionDarkness =
211             getRegionDarkness(
212                 (colors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) !=
213                     WallpaperColors.HINT_SUPPORTS_DARK_TEXT
214             )
215         updateForegroundColor()
216     }
217 }
218 
219 typealias UpdateColorCallback = () -> Unit
220