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