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