• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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 
17 package com.android.systemui.statusbar.lockscreen
18 
19 import android.app.ActivityOptions
20 import android.app.PendingIntent
21 import android.app.smartspace.SmartspaceConfig
22 import android.app.smartspace.SmartspaceManager
23 import android.app.smartspace.SmartspaceSession
24 import android.app.smartspace.SmartspaceTarget
25 import android.content.ContentResolver
26 import android.content.Context
27 import android.content.Intent
28 import android.database.ContentObserver
29 import android.graphics.Rect
30 import android.net.Uri
31 import android.os.Handler
32 import android.os.UserHandle
33 import android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
34 import android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS
35 import android.provider.Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED
36 import android.util.Log
37 import android.view.ContextThemeWrapper
38 import android.view.View
39 import android.view.ViewGroup
40 import androidx.annotation.VisibleForTesting
41 import com.android.keyguard.KeyguardUpdateMonitor
42 import com.android.keyguard.KeyguardUpdateMonitorCallback
43 import com.android.settingslib.Utils
44 import com.android.systemui.Dumpable
45 import com.android.systemui.Flags.smartspaceLockscreenViewmodel
46 import com.android.systemui.dagger.SysUISingleton
47 import com.android.systemui.dagger.qualifiers.Background
48 import com.android.systemui.dagger.qualifiers.Main
49 import com.android.systemui.dump.DumpManager
50 import com.android.systemui.flags.FeatureFlags
51 import com.android.systemui.flags.Flags
52 import com.android.systemui.keyguard.WakefulnessLifecycle
53 import com.android.systemui.plugins.ActivityStarter
54 import com.android.systemui.plugins.BcSmartspaceConfigPlugin
55 import com.android.systemui.plugins.BcSmartspaceDataPlugin
56 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
57 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
58 import com.android.systemui.plugins.BcSmartspaceDataPlugin.TimeChangedDelegate
59 import com.android.systemui.plugins.FalsingManager
60 import com.android.systemui.plugins.clocks.WeatherData
61 import com.android.systemui.plugins.statusbar.StatusBarStateController
62 import com.android.systemui.res.R
63 import com.android.systemui.settings.UserTracker
64 import com.android.systemui.shade.ShadeDisplayAware
65 import com.android.systemui.shared.regionsampling.RegionSampler
66 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
67 import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
68 import com.android.systemui.smartspace.ui.binder.SmartspaceViewBinder
69 import com.android.systemui.smartspace.ui.viewmodel.SmartspaceViewModel
70 import com.android.systemui.statusbar.phone.KeyguardBypassController
71 import com.android.systemui.statusbar.policy.ConfigurationController
72 import com.android.systemui.statusbar.policy.DeviceProvisionedController
73 import com.android.systemui.util.asIndenting
74 import com.android.systemui.util.concurrency.Execution
75 import com.android.systemui.util.printCollection
76 import com.android.systemui.util.settings.SecureSettings
77 import com.android.systemui.util.time.SystemClock
78 import java.io.PrintWriter
79 import java.time.Instant
80 import java.util.Deque
81 import java.util.LinkedList
82 import java.util.Optional
83 import java.util.concurrent.Executor
84 import javax.inject.Inject
85 import javax.inject.Named
86 
87 /** Controller for managing the smartspace view on the lockscreen */
88 @SysUISingleton
89 class LockscreenSmartspaceController
90 @Inject
91 constructor(
92     @ShadeDisplayAware private val context: Context,
93     private val featureFlags: FeatureFlags,
94     private val activityStarter: ActivityStarter,
95     private val falsingManager: FalsingManager,
96     private val systemClock: SystemClock,
97     private val secureSettings: SecureSettings,
98     private val userTracker: UserTracker,
99     private val contentResolver: ContentResolver,
100     @ShadeDisplayAware private val configurationController: ConfigurationController,
101     private val statusBarStateController: StatusBarStateController,
102     private val deviceProvisionedController: DeviceProvisionedController,
103     private val bypassController: KeyguardBypassController,
104     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
105     private val wakefulnessLifecycle: WakefulnessLifecycle,
106     private val smartspaceViewModelFactory: SmartspaceViewModel.Factory,
107     private val dumpManager: DumpManager,
108     private val execution: Execution,
109     @Main private val uiExecutor: Executor,
110     @Background private val bgExecutor: Executor,
111     @Main private val handler: Handler,
112     @Background private val bgHandler: Handler,
113     @Named(DATE_SMARTSPACE_DATA_PLUGIN) optionalDatePlugin: Optional<BcSmartspaceDataPlugin>,
114     @Named(WEATHER_SMARTSPACE_DATA_PLUGIN) optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
115     optionalPlugin: Optional<BcSmartspaceDataPlugin>,
116     optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
117 ) : Dumpable {
118     companion object {
119         private const val TAG = "LockscreenSmartspaceController"
120 
121         private const val MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP = 5
122     }
123 
124     private var userSmartspaceManager: SmartspaceManager? = null
125     private var session: SmartspaceSession? = null
126     private val datePlugin: BcSmartspaceDataPlugin? = optionalDatePlugin.orElse(null)
127     private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
128     private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
129     private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
130 
131     // This stores recently received Smartspace pushes to be included in dumpsys.
132     private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList()
133 
134     // Smartspace can be used on multiple displays, such as when the user casts their screen
135     @VisibleForTesting var smartspaceViews = mutableSetOf<SmartspaceView>()
136     private var regionSamplers = mutableMapOf<SmartspaceView, RegionSampler>()
137 
138     private val regionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING)
139     private var isRegionSamplersCreated = false
140     private var showNotifications = false
141     private var showSensitiveContentForCurrentUser = false
142     private var showSensitiveContentForManagedUser = false
143     private var managedUserHandle: UserHandle? = null
144     private var mSplitShadeEnabled = false
145 
146     var suppressDisconnects = false
147         set(value) {
148             field = value
149             disconnect()
150         }
151 
152     // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
153     //  how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
154 
155     // TODO: Move logic into SmartspaceView
156     var stateChangeListener =
157         object : View.OnAttachStateChangeListener {
158             override fun onViewAttachedToWindow(v: View) {
159                 (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled)
160                 smartspaceViews.add(v as SmartspaceView)
161 
162                 connectSession()
163 
164                 updateTextColorFromWallpaper()
165                 statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
166 
167                 if (regionSamplingEnabled && (!regionSamplers.containsKey(v))) {
168                     var regionSampler =
169                         RegionSampler(
170                             v as View,
171                             uiExecutor,
172                             bgExecutor,
173                             regionSamplingEnabled,
174                             isLockscreen = true,
175                         ) {
176                             updateTextColorFromRegionSampler()
177                         }
178                     initializeTextColors(regionSampler)
179                     regionSamplers[v] = regionSampler
180                     regionSampler.startRegionSampler()
181                 }
182             }
183 
184             override fun onViewDetachedFromWindow(v: View) {
185                 smartspaceViews.remove(v as SmartspaceView)
186 
187                 regionSamplers[v]?.stopRegionSampler()
188                 regionSamplers.remove(v as SmartspaceView)
189 
190                 if (smartspaceViews.isEmpty()) {
191                     disconnect()
192                 }
193             }
194         }
195 
196     private val sessionListener =
197         SmartspaceSession.OnTargetsAvailableListener { targets ->
198             execution.assertIsMainThread()
199 
200             // The weather data plugin takes unfiltered targets and performs the filtering
201             // internally.
202             weatherPlugin?.onTargetsAvailable(targets)
203 
204             val now = Instant.ofEpochMilli(systemClock.currentTimeMillis())
205             val weatherTarget =
206                 targets.find { t ->
207                     t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
208                         now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) &&
209                         now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis))
210                 }
211             if (weatherTarget != null) {
212                 val clickIntent = weatherTarget.headerAction?.intent
213                 val weatherData =
214                     weatherTarget.baseAction?.extras?.let { extras ->
215                         WeatherData.fromBundle(extras) { _ ->
216                             if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
217                                 activityStarter.startActivity(
218                                     clickIntent,
219                                     true, /* dismissShade */
220                                     null,
221                                     false,
222                                 )
223                             }
224                         }
225                     }
226 
227                 if (weatherData != null) {
228                     keyguardUpdateMonitor.sendWeatherData(weatherData)
229                 }
230             }
231 
232             val filteredTargets = targets.filter(::filterSmartspaceTarget)
233 
234             synchronized(recentSmartspaceData) {
235                 recentSmartspaceData.offerLast(filteredTargets)
236                 if (recentSmartspaceData.size > MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP) {
237                     recentSmartspaceData.pollFirst()
238                 }
239             }
240 
241             plugin?.onTargetsAvailable(filteredTargets)
242         }
243 
244     private val userTrackerCallback =
245         object : UserTracker.Callback {
246             override fun onUserChanged(newUser: Int, userContext: Context) {
247                 execution.assertIsMainThread()
248                 reloadSmartspace()
249             }
250         }
251 
252     private val settingsObserver =
253         object : ContentObserver(handler) {
254             override fun onChange(selfChange: Boolean, uri: Uri?) {
255                 execution.assertIsMainThread()
256                 reloadSmartspace()
257             }
258         }
259 
260     private val configChangeListener =
261         object : ConfigurationController.ConfigurationListener {
262             override fun onThemeChanged() {
263                 execution.assertIsMainThread()
264                 updateTextColorFromWallpaper()
265             }
266         }
267 
268     private val statusBarStateListener =
269         object : StatusBarStateController.StateListener {
270             override fun onDozeAmountChanged(linear: Float, eased: Float) {
271                 execution.assertIsMainThread()
272                 smartspaceViews.forEach { it.setDozeAmount(eased) }
273             }
274 
275             override fun onDozingChanged(isDozing: Boolean) {
276                 execution.assertIsMainThread()
277                 smartspaceViews.forEach { it.setDozing(isDozing) }
278             }
279         }
280 
281     private val deviceProvisionedListener =
282         object : DeviceProvisionedController.DeviceProvisionedListener {
283             override fun onDeviceProvisionedChanged() {
284                 connectSession()
285             }
286 
287             override fun onUserSetupChanged() {
288                 connectSession()
289             }
290         }
291 
292     private val bypassStateChangedListener =
293         object : KeyguardBypassController.OnBypassStateChangedListener {
294             override fun onBypassStateChanged(isEnabled: Boolean) {
295                 updateBypassEnabled()
296             }
297         }
298 
299     // TODO(b/331451011): Refactor to viewmodel and use interactor pattern.
300     private val wakefulnessLifecycleObserver =
301         object : WakefulnessLifecycle.Observer {
302             override fun onStartedWakingUp() {
303                 smartspaceViews.forEach { it.setScreenOn(true) }
304             }
305 
306             override fun onFinishedGoingToSleep() {
307                 smartspaceViews.forEach { it.setScreenOn(false) }
308             }
309         }
310 
311     init {
312         deviceProvisionedController.addCallback(deviceProvisionedListener)
313         dumpManager.registerDumpable(this)
314     }
315 
316     val isEnabled: Boolean = plugin != null
317 
318     val isDateWeatherDecoupled: Boolean = datePlugin != null && weatherPlugin != null
319 
320     val isWeatherEnabled: Boolean
321         get() {
322             val showWeather =
323                 secureSettings.getIntForUser(LOCK_SCREEN_WEATHER_ENABLED, 1, userTracker.userId) ==
324                     1
325             return showWeather
326         }
327 
328     private fun updateBypassEnabled() {
329         val bypassEnabled = bypassController.bypassEnabled
330         smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
331     }
332 
333     /** Constructs the date view and connects it to the smartspace service. */
334     fun buildAndConnectDateView(parent: ViewGroup, isLargeClock: Boolean): View? {
335         execution.assertIsMainThread()
336 
337         if (!isEnabled) {
338             throw RuntimeException("Cannot build view when not enabled")
339         }
340         if (!isDateWeatherDecoupled) {
341             throw RuntimeException("Cannot build date view when not decoupled")
342         }
343 
344         val view =
345             buildView(
346                 surfaceName = SmartspaceViewModel.SURFACE_DATE_VIEW,
347                 parent = parent,
348                 plugin = datePlugin,
349                 isLargeClock = isLargeClock
350             )
351         connectSession()
352 
353         return view
354     }
355 
356     /** Constructs the weather view and connects it to the smartspace service. */
357     fun buildAndConnectWeatherView(parent: ViewGroup, isLargeClock: Boolean): View? {
358         execution.assertIsMainThread()
359 
360         if (!isEnabled) {
361             throw RuntimeException("Cannot build view when not enabled")
362         }
363         if (!isDateWeatherDecoupled) {
364             throw RuntimeException("Cannot build weather view when not decoupled")
365         }
366 
367         val view =
368             buildView(
369                 surfaceName = SmartspaceViewModel.SURFACE_WEATHER_VIEW,
370                 parent = parent,
371                 plugin = weatherPlugin,
372                 isLargeClock = isLargeClock,
373             )
374         connectSession()
375 
376         return view
377     }
378 
379     /** Constructs the smartspace view and connects it to the smartspace service. */
380     fun buildAndConnectView(parent: ViewGroup): View? {
381         execution.assertIsMainThread()
382 
383         if (!isEnabled) {
384             throw RuntimeException("Cannot build view when not enabled")
385         }
386 
387         configPlugin?.let { plugin?.registerConfigProvider(it) }
388 
389         val view =
390             buildView(
391                 surfaceName = SmartspaceViewModel.SURFACE_GENERAL_VIEW,
392                 parent = parent,
393                 plugin = plugin,
394                 configPlugin = configPlugin,
395                 isLargeClock = false,
396             )
397         connectSession()
398 
399         return view
400     }
401 
402     private fun buildView(
403         surfaceName: String,
404         parent: ViewGroup,
405         plugin: BcSmartspaceDataPlugin?,
406         configPlugin: BcSmartspaceConfigPlugin? = null,
407         isLargeClock: Boolean,
408     ): View? {
409         if (plugin == null) {
410             return null
411         }
412 
413         val ssView = if (isLargeClock) plugin.getLargeClockView(parent) else plugin.getView(parent)
414         configPlugin?.let { ssView.registerConfigProvider(it) }
415         ssView.setBgHandler(bgHandler)
416         ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
417         ssView.setTimeChangedDelegate(SmartspaceTimeChangedDelegate(keyguardUpdateMonitor))
418         ssView.registerDataProvider(plugin)
419 
420         ssView.setIntentStarter(
421             object : BcSmartspaceDataPlugin.IntentStarter {
422                 override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
423                     if (showOnLockscreen) {
424                         activityStarter.startActivity(
425                             intent,
426                             true, /* dismissShade */
427                             // launch animator - looks bad with the transparent smartspace bg
428                             null,
429                             true,
430                         )
431                     } else {
432                         activityStarter.postStartActivityDismissingKeyguard(intent, 0)
433                     }
434                 }
435 
436                 override fun startPendingIntent(
437                     view: View,
438                     pi: PendingIntent,
439                     showOnLockscreen: Boolean,
440                 ) {
441                     if (showOnLockscreen) {
442                         val options =
443                             ActivityOptions.makeBasic()
444                                 .setPendingIntentBackgroundActivityStartMode(
445                                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
446                                 )
447                                 .toBundle()
448                         pi.send(options)
449                     } else {
450                         activityStarter.postStartActivityDismissingKeyguard(pi)
451                     }
452                 }
453             }
454         )
455         ssView.setFalsingManager(falsingManager)
456         ssView.setKeyguardBypassEnabled(bypassController.bypassEnabled)
457         return (ssView as View).apply {
458             setTag(R.id.tag_smartspace_view, Any())
459             addOnAttachStateChangeListener(stateChangeListener)
460 
461             if (smartspaceLockscreenViewmodel()) {
462                 val viewModel = smartspaceViewModelFactory.create(surfaceName)
463                 SmartspaceViewBinder.bind(smartspaceView = ssView, viewModel = viewModel)
464             }
465         }
466     }
467 
468     private fun connectSession() {
469         if (userSmartspaceManager == null) {
470             userSmartspaceManager =
471                 userTracker.userContext.getSystemService(SmartspaceManager::class.java)
472         }
473         if (userSmartspaceManager == null) return
474         if (datePlugin == null && weatherPlugin == null && plugin == null) return
475         if (session != null || smartspaceViews.isEmpty()) {
476             return
477         }
478 
479         // Only connect after the device is fully provisioned to avoid connection caching
480         // issues
481         if (
482             !deviceProvisionedController.isDeviceProvisioned() ||
483                 !deviceProvisionedController.isCurrentUserSetup()
484         ) {
485             return
486         }
487 
488         val newSession =
489             userSmartspaceManager?.createSmartspaceSession(
490                 SmartspaceConfig.Builder(
491                         userTracker.userContext,
492                         BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD,
493                     )
494                     .build()
495             )
496         Log.d(
497             TAG,
498             "Starting smartspace session for " + BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD,
499         )
500         newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
501         this.session = newSession
502 
503         deviceProvisionedController.removeCallback(deviceProvisionedListener)
504         userTracker.addCallback(userTrackerCallback, uiExecutor)
505         contentResolver.registerContentObserver(
506             secureSettings.getUriFor(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
507             true,
508             settingsObserver,
509             UserHandle.USER_ALL,
510         )
511         contentResolver.registerContentObserver(
512             secureSettings.getUriFor(LOCK_SCREEN_SHOW_NOTIFICATIONS),
513             true,
514             settingsObserver,
515             UserHandle.USER_ALL,
516         )
517         configurationController.addCallback(configChangeListener)
518         statusBarStateController.addCallback(statusBarStateListener)
519         bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
520         if (!smartspaceLockscreenViewmodel()) {
521             wakefulnessLifecycle.addObserver(wakefulnessLifecycleObserver)
522         }
523 
524         datePlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
525         weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
526         plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
527 
528         updateBypassEnabled()
529         reloadSmartspace()
530     }
531 
532     fun setSplitShadeEnabled(enabled: Boolean) {
533         mSplitShadeEnabled = enabled
534         smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) }
535     }
536 
537     /** Requests the smartspace session for an update. */
538     fun requestSmartspaceUpdate() {
539         session?.requestSmartspaceUpdate()
540     }
541 
542     /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
543     fun disconnect() {
544         if (!smartspaceViews.isEmpty()) return
545         if (suppressDisconnects) return
546 
547         execution.assertIsMainThread()
548 
549         if (session == null) {
550             return
551         }
552 
553         session?.let {
554             it.removeOnTargetsAvailableListener(sessionListener)
555             it.close()
556         }
557         userTracker.removeCallback(userTrackerCallback)
558         contentResolver.unregisterContentObserver(settingsObserver)
559         configurationController.removeCallback(configChangeListener)
560         statusBarStateController.removeCallback(statusBarStateListener)
561         bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
562         if (!smartspaceLockscreenViewmodel()) {
563             wakefulnessLifecycle.removeObserver(wakefulnessLifecycleObserver)
564         }
565         session = null
566 
567         datePlugin?.registerSmartspaceEventNotifier(null)
568 
569         weatherPlugin?.registerSmartspaceEventNotifier(null)
570         weatherPlugin?.onTargetsAvailable(emptyList())
571 
572         plugin?.registerSmartspaceEventNotifier(null)
573         plugin?.onTargetsAvailable(emptyList())
574 
575         Log.d(TAG, "Ended smartspace session for lockscreen")
576     }
577 
578     fun addListener(listener: SmartspaceTargetListener) {
579         execution.assertIsMainThread()
580         plugin?.registerListener(listener)
581     }
582 
583     fun removeListener(listener: SmartspaceTargetListener) {
584         execution.assertIsMainThread()
585         plugin?.unregisterListener(listener)
586     }
587 
588     fun isWithinSmartspaceBounds(x: Int, y: Int): Boolean {
589         smartspaceViews.forEach {
590             val bounds = Rect()
591             with(it as View) {
592                 this.getBoundsOnScreen(bounds)
593                 if (bounds.contains(x, y)) {
594                     return true
595                 }
596             }
597         }
598 
599         return false
600     }
601 
602     private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
603         if (isDateWeatherDecoupled && t.featureType == SmartspaceTarget.FEATURE_WEATHER) {
604             return false
605         }
606         if (!showNotifications) {
607             return t.featureType == SmartspaceTarget.FEATURE_WEATHER
608         }
609         return when (t.userHandle) {
610             userTracker.userHandle -> {
611                 !t.isSensitive || showSensitiveContentForCurrentUser
612             }
613             managedUserHandle -> {
614                 // Really, this should be "if this managed profile is associated with the current
615                 // active user", but we don't have a good way to check that, so instead we cheat:
616                 // Only the primary user can have an associated managed profile, so only show
617                 // content for the managed profile if the primary user is active
618                 userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
619                     (!t.isSensitive || showSensitiveContentForManagedUser)
620             }
621             else -> {
622                 false
623             }
624         }
625     }
626 
627     private fun initializeTextColors(regionSampler: RegionSampler) {
628         val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
629         val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
630 
631         val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
632         val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
633 
634         regionSampler.setForegroundColors(lightColor, darkColor)
635     }
636 
637     private fun updateTextColorFromRegionSampler() {
638         regionSamplers.forEach { (view, region) ->
639             val textColor = region.currentForegroundColor()
640             if (textColor != null) {
641                 view.setPrimaryTextColor(textColor)
642             }
643         }
644     }
645 
646     private fun updateTextColorFromWallpaper() {
647         if (!regionSamplingEnabled || regionSamplers.isEmpty()) {
648             val wallpaperTextColor =
649                 Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
650             smartspaceViews.forEach { it.setPrimaryTextColor(wallpaperTextColor) }
651         } else {
652             updateTextColorFromRegionSampler()
653         }
654     }
655 
656     private fun reloadSmartspace() {
657         showNotifications =
658             secureSettings.getIntForUser(LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userTracker.userId) == 1
659 
660         showSensitiveContentForCurrentUser =
661             secureSettings.getIntForUser(
662                 LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
663                 0,
664                 userTracker.userId,
665             ) == 1
666 
667         managedUserHandle = getWorkProfileUser()
668         val managedId = managedUserHandle?.identifier
669         if (managedId != null) {
670             showSensitiveContentForManagedUser =
671                 secureSettings.getIntForUser(
672                     LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
673                     0,
674                     managedId,
675                 ) == 1
676         }
677 
678         session?.requestSmartspaceUpdate()
679     }
680 
681     private fun getWorkProfileUser(): UserHandle? {
682         for (userInfo in userTracker.userProfiles) {
683             if (userInfo.isManagedProfile) {
684                 return userInfo.userHandle
685             }
686         }
687         return null
688     }
689 
690     override fun dump(pw: PrintWriter, args: Array<out String>) {
691         pw.asIndenting().run {
692             printCollection("Region Samplers", regionSamplers.values) { it.dump(this) }
693         }
694 
695         pw.println("Recent BC Smartspace Targets (most recent first)")
696         synchronized(recentSmartspaceData) {
697             if (recentSmartspaceData.size === 0) {
698                 pw.println("   No data\n")
699                 return
700             }
701             recentSmartspaceData.descendingIterator().forEachRemaining { smartspaceTargets ->
702                 pw.println("   Number of targets: ${smartspaceTargets.size}")
703                 for (target in smartspaceTargets) {
704                     pw.println("      $target")
705                 }
706                 pw.println()
707             }
708         }
709     }
710 
711     private class SmartspaceTimeChangedDelegate(
712         private val keyguardUpdateMonitor: KeyguardUpdateMonitor
713     ) : TimeChangedDelegate {
714         private var keyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback? = null
715 
716         override fun register(callback: Runnable) {
717             if (keyguardUpdateMonitorCallback != null) {
718                 unregister()
719             }
720             keyguardUpdateMonitorCallback =
721                 object : KeyguardUpdateMonitorCallback() {
722                     override fun onTimeChanged() {
723                         callback.run()
724                     }
725                 }
726             keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
727             callback.run()
728         }
729 
730         override fun unregister() {
731             keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
732             keyguardUpdateMonitorCallback = null
733         }
734     }
735 }
736