Introduce NotificationIconAreaController interface

Fork implementation and dispatch based on
NOTIFICATION_ICON_CONTAINER_REFACTOR flag.

Bug: 290787599
Test: make
Change-Id: I27f8f2093c856b7e94423bfb0f70c4c34dfa7918
This commit is contained in:
Steve Elliott
2023-07-31 13:24:57 -04:00
parent 59e9f7fe95
commit fbe4d08edb
10 changed files with 949 additions and 9 deletions

View File

@@ -107,6 +107,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationRowCom
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LetterboxModule;
import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -184,6 +185,7 @@ import javax.inject.Named;
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,
MotionToolModule.class,
NotificationIconAreaControllerModule.class,
PeopleHubModule.class,
PeopleModule.class,
PluginModule.class,

View File

@@ -0,0 +1,673 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.os.Trace
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.annotation.VisibleForTesting
import androidx.collection.ArrayMap
import com.android.app.animation.Interpolators
import com.android.internal.statusbar.StatusBarIcon
import com.android.internal.util.ContrastColorUtil
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.demomode.DemoMode
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.flags.ViewRefactorFlag
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import java.util.function.Function
import javax.inject.Inject
/**
* Controller class for [NotificationIconContainer]. This implementation serves as a temporary
* wrapper around [NotificationIconContainerViewBinder], so that external code can continue to
* depend on the [NotificationIconAreaController] interface. Once
* [LegacyNotificationIconAreaControllerImpl] is removed, this class can go away and the ViewBinder
* can be used directly.
*/
@SysUISingleton
class NotificationIconAreaControllerViewBinderWrapperImpl
@Inject
constructor(
private val context: Context,
private val statusBarStateController: StatusBarStateController,
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
private val bypassController: KeyguardBypassController,
private val mediaManager: NotificationMediaManager,
notificationListener: NotificationListener,
private val dozeParameters: DozeParameters,
private val sectionStyleProvider: SectionStyleProvider,
private val bubblesOptional: Optional<Bubbles>,
demoModeController: DemoModeController,
darkIconDispatcher: DarkIconDispatcher,
featureFlags: FeatureFlags,
private val statusBarWindowController: StatusBarWindowController,
private val screenOffAnimationController: ScreenOffAnimationController,
) :
NotificationIconAreaController,
DarkIconDispatcher.DarkReceiver,
StatusBarStateController.StateListener,
NotificationWakeUpCoordinator.WakeUpListener,
DemoMode {
private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
private val shelfRefactor = ViewRefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
private val tintAreas = ArrayList<Rect>()
private var iconSize = 0
private var iconHPadding = 0
private var iconTint = Color.WHITE
private var notificationEntries = listOf<ListEntry>()
private var notificationIconArea: View? = null
private var notificationIcons: NotificationIconContainer? = null
private var shelfIcons: NotificationIconContainer? = null
private var aodIcons: NotificationIconContainer? = null
private var aodIconAppearTranslation = 0
private var animationsEnabled = false
private var aodIconTint = 0
private var aodIconsVisible = false
private var showLowPriority = true
@VisibleForTesting
val settingsListener: NotificationListener.NotificationSettingsListener =
object : NotificationListener.NotificationSettingsListener {
override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
showLowPriority = !hideSilentStatusIcons
updateStatusBarIcons()
}
}
init {
statusBarStateController.addCallback(this)
wakeUpCoordinator.addListener(this)
demoModeController.addCallback(this)
notificationListener.addNotificationSettingsListener(settingsListener)
initializeNotificationAreaViews(context)
reloadAodColor()
darkIconDispatcher.addDarkReceiver(this)
}
@VisibleForTesting
fun shouldShowLowPriorityIcons(): Boolean {
return showLowPriority
}
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
override fun setupAodIcons(aodIcons: NotificationIconContainer) {
val changed = this.aodIcons != null && aodIcons !== this.aodIcons
if (changed) {
this.aodIcons!!.setAnimationsEnabled(false)
this.aodIcons!!.removeAllViews()
}
this.aodIcons = aodIcons
this.aodIcons!!.setOnLockScreen(true)
updateAodIconsVisibility(animate = false, forceUpdate = changed)
updateAnimations()
if (changed) {
updateAodNotificationIcons()
}
updateIconLayoutParams(context)
}
override fun setupShelf(notificationShelfController: NotificationShelfController) {
shelfRefactor.assertDisabled()
shelfIcons = notificationShelfController.shelfIcons
}
override fun setShelfIcons(icons: NotificationIconContainer) {
if (shelfRefactor.expectEnabled()) {
shelfIcons = icons
}
}
override fun onDensityOrFontScaleChanged(context: Context) {
updateIconLayoutParams(context)
}
/** Returns the view that represents the notification area. */
override fun getNotificationInnerAreaView(): View? {
return notificationIconArea
}
/**
* See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
* color that should be used to tint any icons in the notification area.
*
* @param tintAreas the areas in which to tint the icons, specified in screen coordinates
* @param darkIntensity
*/
override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
this.tintAreas.clear()
this.tintAreas.addAll(tintAreas)
if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
this.iconTint = iconTint
}
applyNotificationIconsTint()
}
/** Updates the notifications with the given list of notifications to display. */
override fun updateNotificationIcons(entries: List<ListEntry>) {
notificationEntries = entries
updateNotificationIcons()
}
private fun updateStatusBarIcons() {
updateIconsForLayout(
{ entry: NotificationEntry -> entry.icons.statusBarIcon },
notificationIcons,
showAmbient = false /* showAmbient */,
showLowPriority = showLowPriority,
hideDismissed = true /* hideDismissed */,
hideRepliedMessages = true /* hideRepliedMessages */,
hideCurrentMedia = false /* hideCurrentMedia */,
hidePulsing = false /* hidePulsing */
)
}
override fun updateAodNotificationIcons() {
if (aodIcons == null) {
return
}
updateIconsForLayout(
{ entry: NotificationEntry -> entry.icons.aodIcon },
aodIcons,
showAmbient = false /* showAmbient */,
showLowPriority = true /* showLowPriority */,
hideDismissed = true /* hideDismissed */,
hideRepliedMessages = true /* hideRepliedMessages */,
hideCurrentMedia = true /* hideCurrentMedia */,
hidePulsing = bypassController.bypassEnabled /* hidePulsing */
)
}
override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
notificationIcons!!.showIconIsolated(icon, animated)
}
override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
}
override fun onDozingChanged(isDozing: Boolean) {
if (aodIcons == null) {
return
}
val animate = (dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking)
aodIcons!!.setDozing(isDozing, animate, 0)
}
override fun setAnimationsEnabled(enabled: Boolean) {
animationsEnabled = enabled
updateAnimations()
}
override fun onStateChanged(newState: Int) {
updateAodIconsVisibility(animate = false, forceUpdate = false)
updateAnimations()
}
override fun onThemeChanged() {
reloadAodColor()
updateAodIconColors()
}
override fun getHeight(): Int {
return if (aodIcons == null) 0 else aodIcons!!.height
}
@VisibleForTesting
fun appearAodIcons() {
if (aodIcons == null) {
return
}
if (screenOffAnimationController.shouldAnimateAodIcons()) {
aodIcons!!.translationY = -aodIconAppearTranslation.toFloat()
aodIcons!!.alpha = 0f
animateInAodIconTranslation()
aodIcons!!
.animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
.start()
} else {
aodIcons!!.alpha = 1.0f
aodIcons!!.translationY = 0f
}
}
override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
var animate = true
if (!bypassController.bypassEnabled) {
animate = dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking
// We only want the appear animations to happen when the notifications get fully hidden,
// since otherwise the unhide animation overlaps
animate = animate and isFullyHidden
}
updateAodIconsVisibility(animate, false /* force */)
updateAodNotificationIcons()
updateAodIconColors()
}
override fun onPulseExpansionChanged(expandingChanged: Boolean) {
if (expandingChanged) {
updateAodIconsVisibility(animate = true, forceUpdate = false)
}
}
override fun demoCommands(): List<String> {
val commands = ArrayList<String>()
commands.add(DemoMode.COMMAND_NOTIFICATIONS)
return commands
}
override fun dispatchDemoCommand(command: String, args: Bundle) {
if (notificationIconArea != null) {
val visible = args.getString("visible")
val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
notificationIconArea?.visibility = vis
}
}
override fun onDemoModeFinished() {
if (notificationIconArea != null) {
notificationIconArea?.visibility = View.VISIBLE
}
}
private fun inflateIconArea(inflater: LayoutInflater): View {
return inflater.inflate(R.layout.notification_icon_area, null)
}
/** Initializes the views that will represent the notification area. */
private fun initializeNotificationAreaViews(context: Context) {
reloadDimens(context)
val layoutInflater = LayoutInflater.from(context)
notificationIconArea = inflateIconArea(layoutInflater)
notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
}
private fun updateIconLayoutParams(context: Context) {
reloadDimens(context)
val params = generateIconLayoutParams()
for (i in 0 until notificationIcons!!.childCount) {
val child = notificationIcons!!.getChildAt(i)
child.layoutParams = params
}
if (shelfIcons != null) {
for (i in 0 until shelfIcons!!.childCount) {
val child = shelfIcons!!.getChildAt(i)
child.layoutParams = params
}
}
if (aodIcons != null) {
for (i in 0 until aodIcons!!.childCount) {
val child = aodIcons!!.getChildAt(i)
child.layoutParams = params
}
}
}
private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
return FrameLayout.LayoutParams(
iconSize + 2 * iconHPadding,
statusBarWindowController.statusBarHeight
)
}
private fun reloadDimens(context: Context) {
val res = context.resources
iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
}
private fun shouldShowNotificationIcon(
entry: NotificationEntry,
showAmbient: Boolean,
showLowPriority: Boolean,
hideDismissed: Boolean,
hideRepliedMessages: Boolean,
hideCurrentMedia: Boolean,
hidePulsing: Boolean
): Boolean {
if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
return false
}
if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
return false
}
if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
return false
}
if (entry.isRowDismissed && hideDismissed) {
return false
}
if (hideRepliedMessages && entry.isLastMessageFromReply) {
return false
}
// showAmbient == show in shade but not shelf
if (!showAmbient && entry.shouldSuppressStatusBar()) {
return false
}
if (
hidePulsing &&
entry.showingPulsing() &&
(!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
) {
return false
}
return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
false
} else true
}
private fun updateNotificationIcons() {
Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
updateStatusBarIcons()
updateShelfIcons()
updateAodNotificationIcons()
applyNotificationIconsTint()
Trace.endSection()
}
private fun updateShelfIcons() {
if (shelfIcons == null) {
return
}
updateIconsForLayout(
{ entry: NotificationEntry -> entry.icons.shelfIcon },
shelfIcons,
showAmbient = true,
showLowPriority = true,
hideDismissed = false,
hideRepliedMessages = false,
hideCurrentMedia = false,
hidePulsing = false
)
}
/**
* Updates the notification icons for a host layout. This will ensure that the notification host
* layout will have the same icons like the ones in here.
*
* @param function A function to look up an icon view based on an entry
* @param hostLayout which layout should be updated
* @param showAmbient should ambient notification icons be shown
* @param showLowPriority should icons from silent notifications be shown
* @param hideDismissed should dismissed icons be hidden
* @param hideRepliedMessages should messages that have been replied to be hidden
* @param hidePulsing should pulsing notifications be hidden
*/
private fun updateIconsForLayout(
function: Function<NotificationEntry, StatusBarIconView?>,
hostLayout: NotificationIconContainer?,
showAmbient: Boolean,
showLowPriority: Boolean,
hideDismissed: Boolean,
hideRepliedMessages: Boolean,
hideCurrentMedia: Boolean,
hidePulsing: Boolean,
) {
val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
// Filter out ambient notifications and notification children.
for (i in notificationEntries.indices) {
val entry = notificationEntries[i].representativeEntry
if (entry != null && entry.row != null) {
if (
shouldShowNotificationIcon(
entry,
showAmbient,
showLowPriority,
hideDismissed,
hideRepliedMessages,
hideCurrentMedia,
hidePulsing
)
) {
val iconView = function.apply(entry)
if (iconView != null) {
toShow.add(iconView)
}
}
}
}
// In case we are changing the suppression of a group, the replacement shouldn't flicker
// and it should just be replaced instead. We therefore look for notifications that were
// just replaced by the child or vice-versa to suppress this.
val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
val toRemove = ArrayList<View>()
for (i in 0 until hostLayout!!.childCount) {
val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
if (!toShow.contains(child)) {
var iconWasReplaced = false
val removedGroupKey = child.notification.groupKey
for (j in toShow.indices) {
val candidate = toShow[j]
if (
candidate.sourceIcon.sameAs(child.sourceIcon) &&
candidate.notification.groupKey == removedGroupKey
) {
if (!iconWasReplaced) {
iconWasReplaced = true
} else {
iconWasReplaced = false
break
}
}
}
if (iconWasReplaced) {
var statusBarIcons = replacingIcons[removedGroupKey]
if (statusBarIcons == null) {
statusBarIcons = ArrayList()
replacingIcons[removedGroupKey] = statusBarIcons
}
statusBarIcons.add(child.statusBarIcon)
}
toRemove.add(child)
}
}
// removing all duplicates
val duplicates = ArrayList<String?>()
for (key in replacingIcons.keys) {
val statusBarIcons = replacingIcons[key]!!
if (statusBarIcons.size != 1) {
duplicates.add(key)
}
}
replacingIcons.removeAll(duplicates)
hostLayout.setReplacingIcons(replacingIcons)
val toRemoveCount = toRemove.size
for (i in 0 until toRemoveCount) {
hostLayout.removeView(toRemove[i])
}
val params = generateIconLayoutParams()
for (i in toShow.indices) {
val v = toShow[i]
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v)
if (v.parent == null) {
if (hideDismissed) {
v.setOnDismissListener(updateStatusBarIcons)
}
hostLayout.addView(v, i, params)
}
}
hostLayout.setChangingViewPositions(true)
// Re-sort notification icons
val childCount = hostLayout.childCount
for (i in 0 until childCount) {
val actual = hostLayout.getChildAt(i)
val expected = toShow[i]
if (actual === expected) {
continue
}
hostLayout.removeView(expected)
hostLayout.addView(expected, i)
}
hostLayout.setChangingViewPositions(false)
hostLayout.setReplacingIcons(null)
}
/** Applies [.mIconTint] to the notification icons. */
private fun applyNotificationIconsTint() {
for (i in 0 until notificationIcons!!.childCount) {
val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
if (iv.width != 0) {
updateTintForIcon(iv, iconTint)
} else {
iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
}
}
updateAodIconColors()
}
private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
var color = StatusBarIconView.NO_COLOR
val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
if (colorize) {
color = DarkIconDispatcher.getTint(tintAreas, v, tint)
}
v.staticDrawableColor = color
v.setDecorColor(tint)
}
private fun updateAnimations() {
val inShade = statusBarStateController.state == StatusBarState.SHADE
if (aodIcons != null) {
aodIcons!!.setAnimationsEnabled(animationsEnabled && !inShade)
}
notificationIcons!!.setAnimationsEnabled(animationsEnabled && inShade)
}
private fun animateInAodIconTranslation() {
aodIcons!!
.animate()
.setInterpolator(Interpolators.DECELERATE_QUINT)
.translationY(0f)
.setDuration(AOD_ICONS_APPEAR_DURATION)
.start()
}
private fun reloadAodColor() {
aodIconTint =
Utils.getColorAttrDefaultColor(
context,
R.attr.wallpaperTextColor,
DEFAULT_AOD_ICON_COLOR
)
}
private fun updateAodIconColors() {
if (aodIcons != null) {
for (i in 0 until aodIcons!!.childCount) {
val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
if (iv.width != 0) {
updateTintForIcon(iv, aodIconTint)
} else {
iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
}
}
}
}
private fun updateAodIconsVisibility(animate: Boolean, forceUpdate: Boolean) {
if (aodIcons == null) {
return
}
var visible = (bypassController.bypassEnabled || wakeUpCoordinator.notificationsFullyHidden)
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off animation is
// playing, in which case we want them to be visible since we're animating in the AOD UI and
// will be switching to KEYGUARD shortly.
if (
statusBarStateController.state != StatusBarState.KEYGUARD &&
!screenOffAnimationController.shouldShowAodIconsWhenShade()
) {
visible = false
}
if (visible && wakeUpCoordinator.isPulseExpanding() && !bypassController.bypassEnabled) {
visible = false
}
if (aodIconsVisible != visible || forceUpdate) {
aodIconsVisible = visible
aodIcons!!.animate().cancel()
if (animate) {
val wasFullyInvisible = aodIcons!!.visibility != View.VISIBLE
if (aodIconsVisible) {
if (wasFullyInvisible) {
// No fading here, let's just appear the icons instead!
aodIcons!!.visibility = View.VISIBLE
aodIcons!!.alpha = 1.0f
appearAodIcons()
} else {
// Let's make sure the icon are translated to 0, since we cancelled it above
animateInAodIconTranslation()
// We were fading out, let's fade in instead
CrossFadeHelper.fadeIn(aodIcons)
}
} else {
// Let's make sure the icon are translated to 0, since we cancelled it above
animateInAodIconTranslation()
CrossFadeHelper.fadeOut(aodIcons)
}
} else {
aodIcons!!.alpha = 1.0f
aodIcons!!.translationY = 0f
aodIcons!!.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
}
}
companion object {
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
@ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
import com.android.systemui.statusbar.phone.NotificationIconContainer
/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
object NotificationIconContainerViewBinder {
fun bind(
view: NotificationIconContainer,
viewModel: NotificationIconContainerViewModel,
) {
view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) {} }
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
/**
* View-model for the row of notification icons displayed in the NotificationShelf, StatusBar, and
* AOD.
*/
interface NotificationIconContainerViewModel

View File

@@ -28,7 +28,6 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
@@ -150,6 +149,7 @@ import javax.inject.Named;
public class NotificationStackScrollLayoutController {
private static final String TAG = "StackScrollerController";
private static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final String HIGH_PRIORITY = "high_priority";
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;

View File

@@ -1,3 +1,18 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone;
import android.content.Context;
@@ -43,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.provider.SectionSt
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.wm.shell.bubbles.Bubbles;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -55,13 +72,13 @@ import javax.inject.Inject;
* normally reserved for notifications.
*/
@SysUISingleton
public class NotificationIconAreaController implements
public class LegacyNotificationIconAreaControllerImpl implements
NotificationIconAreaController,
DarkReceiver,
StatusBarStateController.StateListener,
NotificationWakeUpCoordinator.WakeUpListener,
DemoMode {
public static final String HIGH_PRIORITY = "high_priority";
private static final long AOD_ICONS_APPEAR_DURATION = 200;
@ColorInt
private static final int DEFAULT_AOD_ICON_COLOR = 0xffffffff;
@@ -110,7 +127,7 @@ public class NotificationIconAreaController implements
};
@Inject
public NotificationIconAreaController(
public LegacyNotificationIconAreaControllerImpl(
Context context,
StatusBarStateController statusBarStateController,
NotificationWakeUpCoordinator wakeUpCoordinator,
@@ -192,7 +209,7 @@ public class NotificationIconAreaController implements
}
}
public void onDensityOrFontScaleChanged(Context context) {
public void onDensityOrFontScaleChanged(@NotNull Context context) {
updateIconLayoutParams(context);
}
@@ -493,7 +510,7 @@ public class NotificationIconAreaController implements
mNotificationIcons.showIconIsolated(icon, animated);
}
public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) {
public void setIsolatedIconLocation(@NotNull Rect iconDrawingRect, boolean requireStateUpdate) {
mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate);
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone
import android.content.Context
import android.graphics.Rect
import android.view.View
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.ListEntry
/**
* A controller for the space in the status bar to the left of the system icons. This area is
* normally reserved for notifications.
*/
interface NotificationIconAreaController {
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
fun setupAodIcons(aodIcons: NotificationIconContainer)
fun setupShelf(notificationShelfController: NotificationShelfController)
fun setShelfIcons(icons: NotificationIconContainer)
fun onDensityOrFontScaleChanged(context: Context)
/** Returns the view that represents the notification area. */
fun getNotificationInnerAreaView(): View?
/** Updates the notifications with the given list of notifications to display. */
fun updateNotificationIcons(entries: List<@JvmSuppressWildcards ListEntry>)
fun updateAodNotificationIcons()
fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean)
fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean)
fun setAnimationsEnabled(enabled: Boolean)
fun onThemeChanged()
fun getHeight(): Int
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.phone
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconAreaControllerViewBinderWrapperImpl
import dagger.Module
import dagger.Provides
import javax.inject.Provider
@Module
object NotificationIconAreaControllerModule {
@Provides
fun provideNotificationIconAreaControllerImpl(
featureFlags: FeatureFlags,
legacyProvider: Provider<LegacyNotificationIconAreaControllerImpl>,
newProvider: Provider<NotificationIconAreaControllerViewBinderWrapperImpl>,
): NotificationIconAreaController =
if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
newProvider.get()
} else {
legacyProvider.get()
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
@Mock private lateinit var notifListener: NotificationListener
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var wakeUpCoordinator: NotificationWakeUpCoordinator
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var notifMediaManager: NotificationMediaManager
@Mock private lateinit var dozeParams: DozeParameters
@Mock private lateinit var sectionStyleProvider: SectionStyleProvider
@Mock private lateinit var darkIconDispatcher: DarkIconDispatcher
@Mock private lateinit var statusBarWindowController: StatusBarWindowController
@Mock private lateinit var screenOffAnimController: ScreenOffAnimationController
@Mock private lateinit var bubbles: Bubbles
@Mock private lateinit var demoModeController: DemoModeController
@Mock private lateinit var aodIcons: NotificationIconContainer
@Mock private lateinit var featureFlags: FeatureFlags
private lateinit var underTest: NotificationIconAreaControllerViewBinderWrapperImpl
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
underTest =
NotificationIconAreaControllerViewBinderWrapperImpl(
mContext,
statusBarStateController,
wakeUpCoordinator,
keyguardBypassController,
notifMediaManager,
notifListener,
dozeParams,
sectionStyleProvider,
Optional.of(bubbles),
demoModeController,
darkIconDispatcher,
featureFlags,
statusBarWindowController,
screenOffAnimController
)
}
@Test
fun testNotificationIcons_settingHideIcons() {
underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
Assert.assertFalse(underTest.shouldShowLowPriorityIcons())
}
@Test
fun testNotificationIcons_settingShowIcons() {
underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
Assert.assertTrue(underTest.shouldShowLowPriorityIcons())
}
@Test
fun testAppearResetsTranslation() {
underTest.setupAodIcons(aodIcons)
whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
underTest.appearAodIcons()
verify(aodIcons).translationY = 0f
verify(aodIcons).alpha = 1.0f
}
}

View File

@@ -48,7 +48,7 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationIconAreaControllerTest extends SysuiTestCase {
public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase {
@Mock
private NotificationListener mListener;
@@ -70,7 +70,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase {
StatusBarWindowController mStatusBarWindowController;
@Mock
ScreenOffAnimationController mScreenOffAnimationController;
private NotificationIconAreaController mController;
private LegacyNotificationIconAreaControllerImpl mController;
@Mock
private Bubbles mBubbles;
@Mock private DemoModeController mDemoModeController;
@@ -82,7 +82,7 @@ public class NotificationIconAreaControllerTest extends SysuiTestCase {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mController = new NotificationIconAreaController(
mController = new LegacyNotificationIconAreaControllerImpl(
mContext,
mStatusBarStateController,
mWakeUpCoordinator,