1 /* 2 * Copyright (C) 2023 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.statusbar.notification.icon.ui.viewbinder 17 18 import android.content.Context 19 import android.graphics.Color 20 import android.graphics.Rect 21 import android.os.Bundle 22 import android.os.Trace 23 import android.view.LayoutInflater 24 import android.view.View 25 import android.widget.FrameLayout 26 import androidx.annotation.ColorInt 27 import androidx.annotation.VisibleForTesting 28 import androidx.collection.ArrayMap 29 import com.android.app.animation.Interpolators 30 import com.android.internal.statusbar.StatusBarIcon 31 import com.android.internal.util.ContrastColorUtil 32 import com.android.settingslib.Utils 33 import com.android.systemui.R 34 import com.android.systemui.dagger.SysUISingleton 35 import com.android.systemui.demomode.DemoMode 36 import com.android.systemui.demomode.DemoModeController 37 import com.android.systemui.flags.FeatureFlags 38 import com.android.systemui.flags.Flags 39 import com.android.systemui.flags.ViewRefactorFlag 40 import com.android.systemui.plugins.DarkIconDispatcher 41 import com.android.systemui.plugins.statusbar.StatusBarStateController 42 import com.android.systemui.statusbar.CrossFadeHelper 43 import com.android.systemui.statusbar.NotificationListener 44 import com.android.systemui.statusbar.NotificationMediaManager 45 import com.android.systemui.statusbar.NotificationShelfController 46 import com.android.systemui.statusbar.StatusBarIconView 47 import com.android.systemui.statusbar.StatusBarState 48 import com.android.systemui.statusbar.notification.NotificationUtils 49 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator 50 import com.android.systemui.statusbar.notification.collection.ListEntry 51 import com.android.systemui.statusbar.notification.collection.NotificationEntry 52 import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider 53 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel 54 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel 55 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel 56 import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl 57 import com.android.systemui.statusbar.phone.DozeParameters 58 import com.android.systemui.statusbar.phone.KeyguardBypassController 59 import com.android.systemui.statusbar.phone.NotificationIconAreaController 60 import com.android.systemui.statusbar.phone.NotificationIconContainer 61 import com.android.systemui.statusbar.phone.ScreenOffAnimationController 62 import com.android.systemui.statusbar.window.StatusBarWindowController 63 import com.android.wm.shell.bubbles.Bubbles 64 import java.util.Optional 65 import java.util.function.Function 66 import javax.inject.Inject 67 import kotlinx.coroutines.DisposableHandle 68 69 /** 70 * Controller class for [NotificationIconContainer]. This implementation serves as a temporary 71 * wrapper around [NotificationIconContainerViewBinder], so that external code can continue to 72 * depend on the [NotificationIconAreaController] interface. Once 73 * [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder 74 * can be used directly. 75 */ 76 @SysUISingleton 77 class NotificationIconAreaControllerViewBinderWrapperImpl 78 @Inject 79 constructor( 80 private val context: Context, 81 private val statusBarStateController: StatusBarStateController, 82 private val wakeUpCoordinator: NotificationWakeUpCoordinator, 83 private val bypassController: KeyguardBypassController, 84 private val mediaManager: NotificationMediaManager, 85 notificationListener: NotificationListener, 86 private val dozeParameters: DozeParameters, 87 private val sectionStyleProvider: SectionStyleProvider, 88 private val bubblesOptional: Optional<Bubbles>, 89 demoModeController: DemoModeController, 90 darkIconDispatcher: DarkIconDispatcher, 91 featureFlags: FeatureFlags, 92 private val statusBarWindowController: StatusBarWindowController, 93 private val screenOffAnimationController: ScreenOffAnimationController, 94 private val shelfIconsViewModel: NotificationIconContainerShelfViewModel, 95 private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel, 96 private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, 97 ) : 98 NotificationIconAreaController, 99 DarkIconDispatcher.DarkReceiver, 100 StatusBarStateController.StateListener, 101 NotificationWakeUpCoordinator.WakeUpListener, 102 DemoMode { 103 104 private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context) 105 private val updateStatusBarIcons = Runnable { updateStatusBarIcons() } 106 private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR) 107 private val tintAreas = ArrayList<Rect>() 108 109 private var iconSize = 0 110 private var iconHPadding = 0 111 private var iconTint = Color.WHITE 112 private var notificationEntries = listOf<ListEntry>() 113 private var notificationIconArea: View? = null 114 private var notificationIcons: NotificationIconContainer? = null 115 private var shelfIcons: NotificationIconContainer? = null 116 private var aodIcons: NotificationIconContainer? = null 117 private var aodBindJob: DisposableHandle? = null 118 private var aodIconAppearTranslation = 0 119 private var animationsEnabled = false 120 private var aodIconTint = 0 121 private var aodIconsVisible = false 122 private var showLowPriority = true 123 124 @VisibleForTesting 125 val settingsListener: NotificationListener.NotificationSettingsListener = 126 object : NotificationListener.NotificationSettingsListener { 127 override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) { 128 showLowPriority = !hideSilentStatusIcons 129 updateStatusBarIcons() 130 } 131 } 132 133 init { 134 statusBarStateController.addCallback(this) 135 wakeUpCoordinator.addListener(this) 136 demoModeController.addCallback(this) 137 notificationListener.addNotificationSettingsListener(settingsListener) 138 initializeNotificationAreaViews(context) 139 reloadAodColor() 140 darkIconDispatcher.addDarkReceiver(this) 141 } 142 143 @VisibleForTesting 144 fun shouldShowLowPriorityIcons(): Boolean { 145 return showLowPriority 146 } 147 148 /** Called by the Keyguard*ViewController whose view contains the aod icons. */ 149 override fun setupAodIcons(aodIcons: NotificationIconContainer) { 150 val changed = this.aodIcons != null && aodIcons !== this.aodIcons 151 if (changed) { 152 this.aodIcons!!.setAnimationsEnabled(false) 153 this.aodIcons!!.removeAllViews() 154 aodBindJob?.dispose() 155 } 156 this.aodIcons = aodIcons 157 this.aodIcons!!.setOnLockScreen(true) 158 aodBindJob = NotificationIconContainerViewBinder.bind(aodIcons, aodIconsViewModel) 159 updateAodIconsVisibility(animate = false, forceUpdate = changed) 160 updateAnimations() 161 if (changed) { 162 updateAodNotificationIcons() 163 } 164 updateIconLayoutParams(context) 165 } 166 167 override fun setupShelf(notificationShelfController: NotificationShelfController) = 168 NotificationShelfViewBinderWrapperControllerImpl.unsupported 169 170 override fun setShelfIcons(icons: NotificationIconContainer) { 171 if (shelfRefactor.expectEnabled()) { 172 NotificationIconContainerViewBinder.bind(icons, shelfIconsViewModel) 173 shelfIcons = icons 174 } 175 } 176 177 override fun onDensityOrFontScaleChanged(context: Context) { 178 updateIconLayoutParams(context) 179 } 180 181 /** Returns the view that represents the notification area. */ 182 override fun getNotificationInnerAreaView(): View? { 183 return notificationIconArea 184 } 185 186 /** 187 * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the 188 * color that should be used to tint any icons in the notification area. 189 * 190 * @param tintAreas the areas in which to tint the icons, specified in screen coordinates 191 * @param darkIntensity 192 */ 193 override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) { 194 this.tintAreas.clear() 195 this.tintAreas.addAll(tintAreas) 196 if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) { 197 this.iconTint = iconTint 198 } 199 applyNotificationIconsTint() 200 } 201 202 /** Updates the notifications with the given list of notifications to display. */ 203 override fun updateNotificationIcons(entries: List<ListEntry>) { 204 notificationEntries = entries 205 updateNotificationIcons() 206 } 207 208 private fun updateStatusBarIcons() { 209 updateIconsForLayout( 210 { entry: NotificationEntry -> entry.icons.statusBarIcon }, 211 notificationIcons, 212 showAmbient = false /* showAmbient */, 213 showLowPriority = showLowPriority, 214 hideDismissed = true /* hideDismissed */, 215 hideRepliedMessages = true /* hideRepliedMessages */, 216 hideCurrentMedia = false /* hideCurrentMedia */, 217 hidePulsing = false /* hidePulsing */ 218 ) 219 } 220 221 override fun updateAodNotificationIcons() { 222 if (aodIcons == null) { 223 return 224 } 225 updateIconsForLayout( 226 { entry: NotificationEntry -> entry.icons.aodIcon }, 227 aodIcons, 228 showAmbient = false /* showAmbient */, 229 showLowPriority = true /* showLowPriority */, 230 hideDismissed = true /* hideDismissed */, 231 hideRepliedMessages = true /* hideRepliedMessages */, 232 hideCurrentMedia = true /* hideCurrentMedia */, 233 hidePulsing = bypassController.bypassEnabled /* hidePulsing */ 234 ) 235 } 236 237 override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) { 238 notificationIcons!!.showIconIsolated(icon, animated) 239 } 240 241 override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) { 242 notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate) 243 } 244 245 override fun onDozingChanged(isDozing: Boolean) { 246 if (aodIcons == null) { 247 return 248 } 249 val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking) 250 aodIcons!!.setDozing(isDozing, animate, 0) 251 } 252 253 override fun setAnimationsEnabled(enabled: Boolean) { 254 animationsEnabled = enabled 255 updateAnimations() 256 } 257 258 override fun onStateChanged(newState: Int) { 259 updateAodIconsVisibility(animate = false, forceUpdate = false) 260 updateAnimations() 261 } 262 263 override fun onThemeChanged() { 264 reloadAodColor() 265 updateAodIconColors() 266 } 267 268 override fun getHeight(): Int { 269 return if (aodIcons == null) 0 else aodIcons!!.height 270 } 271 272 @VisibleForTesting 273 fun appearAodIcons() { 274 if (aodIcons == null) { 275 return 276 } 277 if (screenOffAnimationController.shouldAnimateAodIcons()) { 278 aodIcons!!.translationY = -aodIconAppearTranslation.toFloat() 279 aodIcons!!.alpha = 0f 280 animateInAodIconTranslation() 281 aodIcons!! 282 .animate() 283 .alpha(1f) 284 .setInterpolator(Interpolators.LINEAR) 285 .setDuration(AOD_ICONS_APPEAR_DURATION) 286 .start() 287 } else { 288 aodIcons!!.alpha = 1.0f 289 aodIcons!!.translationY = 0f 290 } 291 } 292 293 override fun onFullyHiddenChanged(isFullyHidden: Boolean) { 294 var animate = true 295 if (!bypassController.bypassEnabled) { 296 animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking 297 // We only want the appear animations to happen when the notifications get fully hidden, 298 // since otherwise the unhide animation overlaps 299 animate = animate and isFullyHidden 300 } 301 updateAodIconsVisibility(animate, false /* force */) 302 updateAodNotificationIcons() 303 updateAodIconColors() 304 } 305 306 override fun onPulseExpansionChanged(expandingChanged: Boolean) { 307 if (expandingChanged) { 308 updateAodIconsVisibility(animate = true, forceUpdate = false) 309 } 310 } 311 312 override fun demoCommands(): List<String> { 313 val commands = ArrayList<String>() 314 commands.add(DemoMode.COMMAND_NOTIFICATIONS) 315 return commands 316 } 317 318 override fun dispatchDemoCommand(command: String, args: Bundle) { 319 if (notificationIconArea != null) { 320 val visible = args.getString("visible") 321 val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE 322 notificationIconArea?.visibility = vis 323 } 324 } 325 326 override fun onDemoModeFinished() { 327 if (notificationIconArea != null) { 328 notificationIconArea?.visibility = View.VISIBLE 329 } 330 } 331 332 private fun inflateIconArea(inflater: LayoutInflater): View { 333 return inflater.inflate(R.layout.notification_icon_area, null) 334 } 335 336 /** Initializes the views that will represent the notification area. */ 337 private fun initializeNotificationAreaViews(context: Context) { 338 reloadDimens(context) 339 val layoutInflater = LayoutInflater.from(context) 340 notificationIconArea = inflateIconArea(layoutInflater) 341 notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons) 342 NotificationIconContainerViewBinder.bind(notificationIcons!!, statusBarIconsViewModel) 343 } 344 345 private fun updateIconLayoutParams(context: Context) { 346 reloadDimens(context) 347 val params = generateIconLayoutParams() 348 for (i in 0 until notificationIcons!!.childCount) { 349 val child = notificationIcons!!.getChildAt(i) 350 child.layoutParams = params 351 } 352 if (shelfIcons != null) { 353 for (i in 0 until shelfIcons!!.childCount) { 354 val child = shelfIcons!!.getChildAt(i) 355 child.layoutParams = params 356 } 357 } 358 if (aodIcons != null) { 359 for (i in 0 until aodIcons!!.childCount) { 360 val child = aodIcons!!.getChildAt(i) 361 child.layoutParams = params 362 } 363 } 364 } 365 366 private fun generateIconLayoutParams(): FrameLayout.LayoutParams { 367 return FrameLayout.LayoutParams( 368 iconSize + 2 * iconHPadding, 369 statusBarWindowController.statusBarHeight 370 ) 371 } 372 373 private fun reloadDimens(context: Context) { 374 val res = context.resources 375 iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp) 376 iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin) 377 aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation) 378 } 379 380 private fun shouldShowNotificationIcon( 381 entry: NotificationEntry, 382 showAmbient: Boolean, 383 showLowPriority: Boolean, 384 hideDismissed: Boolean, 385 hideRepliedMessages: Boolean, 386 hideCurrentMedia: Boolean, 387 hidePulsing: Boolean 388 ): Boolean { 389 if (!showAmbient && sectionStyleProvider.isMinimized(entry)) { 390 return false 391 } 392 if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) { 393 return false 394 } 395 if (!showLowPriority && sectionStyleProvider.isSilent(entry)) { 396 return false 397 } 398 if (entry.isRowDismissed && hideDismissed) { 399 return false 400 } 401 if (hideRepliedMessages && entry.isLastMessageFromReply) { 402 return false 403 } 404 // showAmbient == show in shade but not shelf 405 if (!showAmbient && entry.shouldSuppressStatusBar()) { 406 return false 407 } 408 if ( 409 hidePulsing && 410 entry.showingPulsing() && 411 (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed) 412 ) { 413 return false 414 } 415 return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) { 416 false 417 } else true 418 } 419 420 private fun updateNotificationIcons() { 421 Trace.beginSection("NotificationIconAreaController.updateNotificationIcons") 422 updateStatusBarIcons() 423 updateShelfIcons() 424 updateAodNotificationIcons() 425 applyNotificationIconsTint() 426 Trace.endSection() 427 } 428 429 private fun updateShelfIcons() { 430 if (shelfIcons == null) { 431 return 432 } 433 updateIconsForLayout( 434 { entry: NotificationEntry -> entry.icons.shelfIcon }, 435 shelfIcons, 436 showAmbient = true, 437 showLowPriority = true, 438 hideDismissed = false, 439 hideRepliedMessages = false, 440 hideCurrentMedia = false, 441 hidePulsing = false 442 ) 443 } 444 445 /** 446 * Updates the notification icons for a host layout. This will ensure that the notification host 447 * layout will have the same icons like the ones in here. 448 * 449 * @param function A function to look up an icon view based on an entry 450 * @param hostLayout which layout should be updated 451 * @param showAmbient should ambient notification icons be shown 452 * @param showLowPriority should icons from silent notifications be shown 453 * @param hideDismissed should dismissed icons be hidden 454 * @param hideRepliedMessages should messages that have been replied to be hidden 455 * @param hidePulsing should pulsing notifications be hidden 456 */ 457 private fun updateIconsForLayout( 458 function: Function<NotificationEntry, StatusBarIconView?>, 459 hostLayout: NotificationIconContainer?, 460 showAmbient: Boolean, 461 showLowPriority: Boolean, 462 hideDismissed: Boolean, 463 hideRepliedMessages: Boolean, 464 hideCurrentMedia: Boolean, 465 hidePulsing: Boolean, 466 ) { 467 val toShow = ArrayList<StatusBarIconView>(notificationEntries.size) 468 // Filter out ambient notifications and notification children. 469 for (i in notificationEntries.indices) { 470 val entry = notificationEntries[i].representativeEntry 471 if (entry != null && entry.row != null) { 472 if ( 473 shouldShowNotificationIcon( 474 entry, 475 showAmbient, 476 showLowPriority, 477 hideDismissed, 478 hideRepliedMessages, 479 hideCurrentMedia, 480 hidePulsing 481 ) 482 ) { 483 val iconView = function.apply(entry) 484 if (iconView != null) { 485 toShow.add(iconView) 486 } 487 } 488 } 489 } 490 491 // In case we are changing the suppression of a group, the replacement shouldn't flicker 492 // and it should just be replaced instead. We therefore look for notifications that were 493 // just replaced by the child or vice-versa to suppress this. 494 val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>() 495 val toRemove = ArrayList<View>() 496 for (i in 0 until hostLayout!!.childCount) { 497 val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue 498 if (!toShow.contains(child)) { 499 var iconWasReplaced = false 500 val removedGroupKey = child.notification.groupKey 501 for (j in toShow.indices) { 502 val candidate = toShow[j] 503 if ( 504 candidate.sourceIcon.sameAs(child.sourceIcon) && 505 candidate.notification.groupKey == removedGroupKey 506 ) { 507 if (!iconWasReplaced) { 508 iconWasReplaced = true 509 } else { 510 iconWasReplaced = false 511 break 512 } 513 } 514 } 515 if (iconWasReplaced) { 516 var statusBarIcons = replacingIcons[removedGroupKey] 517 if (statusBarIcons == null) { 518 statusBarIcons = ArrayList() 519 replacingIcons[removedGroupKey] = statusBarIcons 520 } 521 statusBarIcons.add(child.statusBarIcon) 522 } 523 toRemove.add(child) 524 } 525 } 526 // removing all duplicates 527 val duplicates = ArrayList<String?>() 528 for (key in replacingIcons.keys) { 529 val statusBarIcons = replacingIcons[key]!! 530 if (statusBarIcons.size != 1) { 531 duplicates.add(key) 532 } 533 } 534 replacingIcons.removeAll(duplicates) 535 hostLayout.setReplacingIcons(replacingIcons) 536 val toRemoveCount = toRemove.size 537 for (i in 0 until toRemoveCount) { 538 hostLayout.removeView(toRemove[i]) 539 } 540 val params = generateIconLayoutParams() 541 for (i in toShow.indices) { 542 val v = toShow[i] 543 // The view might still be transiently added if it was just removed and added again 544 hostLayout.removeTransientView(v) 545 if (v.parent == null) { 546 if (hideDismissed) { 547 v.setOnDismissListener(updateStatusBarIcons) 548 } 549 hostLayout.addView(v, i, params) 550 } 551 } 552 hostLayout.setChangingViewPositions(true) 553 // Re-sort notification icons 554 val childCount = hostLayout.childCount 555 for (i in 0 until childCount) { 556 val actual = hostLayout.getChildAt(i) 557 val expected = toShow[i] 558 if (actual === expected) { 559 continue 560 } 561 hostLayout.removeView(expected) 562 hostLayout.addView(expected, i) 563 } 564 hostLayout.setChangingViewPositions(false) 565 hostLayout.setReplacingIcons(null) 566 } 567 568 /** Applies [.mIconTint] to the notification icons. */ 569 private fun applyNotificationIconsTint() { 570 for (i in 0 until notificationIcons!!.childCount) { 571 val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView 572 if (iv.width != 0) { 573 updateTintForIcon(iv, iconTint) 574 } else { 575 iv.executeOnLayout { updateTintForIcon(iv, iconTint) } 576 } 577 } 578 updateAodIconColors() 579 } 580 581 private fun updateTintForIcon(v: StatusBarIconView, tint: Int) { 582 val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L) 583 var color = StatusBarIconView.NO_COLOR 584 val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil) 585 if (colorize) { 586 color = DarkIconDispatcher.getTint(tintAreas, v, tint) 587 } 588 v.staticDrawableColor = color 589 v.setDecorColor(tint) 590 } 591 592 private fun updateAnimations() { 593 val inShade = statusBarStateController.state == StatusBarState.SHADE 594 if (aodIcons != null) { 595 aodIcons!!.setAnimationsEnabled(animationsEnabled && !inShade) 596 } 597 notificationIcons!!.setAnimationsEnabled(animationsEnabled && inShade) 598 } 599 600 private fun animateInAodIconTranslation() { 601 aodIcons!! 602 .animate() 603 .setInterpolator(Interpolators.DECELERATE_QUINT) 604 .translationY(0f) 605 .setDuration(AOD_ICONS_APPEAR_DURATION) 606 .start() 607 } 608 609 private fun reloadAodColor() { 610 aodIconTint = 611 Utils.getColorAttrDefaultColor( 612 context, 613 R.attr.wallpaperTextColor, 614 DEFAULT_AOD_ICON_COLOR 615 ) 616 } 617 618 private fun updateAodIconColors() { 619 if (aodIcons != null) { 620 for (i in 0 until aodIcons!!.childCount) { 621 val iv = aodIcons!!.getChildAt(i) as StatusBarIconView 622 if (iv.width != 0) { 623 updateTintForIcon(iv, aodIconTint) 624 } else { 625 iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) } 626 } 627 } 628 } 629 } 630 631 private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) { 632 if (aodIcons == null) { 633 return 634 } 635 var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden) 636 637 // Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is 638 // playing, in which case we want them to be visible since we're animating in the AOD UI and 639 // will be switching to KEYGUARD shortly. 640 if ( 641 statusBarStateController.state != StatusBarState.KEYGUARD && 642 !screenOffAnimationController.shouldShowAodIconsWhenShade() 643 ) { 644 visible = false 645 } 646 if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) { 647 visible = false 648 } 649 if (aodIconsVisible != visible || forceUpdate) { 650 aodIconsVisible = visible 651 aodIcons!!.animate().cancel() 652 if (animate) { 653 val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE 654 if (aodIconsVisible) { 655 if (wasFullyInvisible) { 656 // No fading here, let's just appear the icons instead! 657 aodIcons!!.visibility = View.VISIBLE 658 aodIcons!!.alpha = 1.0f 659 appearAodIcons() 660 } else { 661 // Let's make sure the icon are translated to 0, since we cancelled it above 662 animateInAodIconTranslation() 663 // We were fading out, let's fade in instead 664 CrossFadeHelper.fadeIn(aodIcons) 665 } 666 } else { 667 // Let's make sure the icon are translated to 0, since we cancelled it above 668 animateInAodIconTranslation() 669 CrossFadeHelper.fadeOut(aodIcons) 670 } 671 } else { 672 aodIcons!!.alpha = 1.0f 673 aodIcons!!.translationY = 0f 674 aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE 675 } 676 } 677 } 678 679 companion object { 680 private const val AOD_ICONS_APPEAR_DURATION: Long = 200 681 682 @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1 683 } 684 } 685