1 /*
<lambda>null2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 *
14 */
15
16 package com.android.systemui.statusbar.notification.icon.ui.viewmodel
17
18 import android.graphics.drawable.Icon
19 import androidx.collection.ArrayMap
20 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
21 import com.android.systemui.util.kotlin.mapValuesNotNullTo
22
23 /** Encapsulates the collection of notification icons present on the device. */
24 data class NotificationIconsViewData(
25 /** Icons that are visible in the container. */
26 val visibleIcons: List<NotificationIconInfo> = emptyList(),
27 /** Limit applied to the [visibleIcons]; can be interpreted different based on [limitType]. */
28 val iconLimit: Int = visibleIcons.size,
29 /** How [iconLimit] is applied to [visibleIcons]. */
30 val limitType: LimitType = LimitType.MaximumAmount,
31 ) {
32 // TODO(b/305739416): This can be removed once we are no longer looking up the StatusBarIconView
33 // instances from outside of the view-binder layer. Ideally, we would just use MaximumAmount,
34 // and apply it directly to the list of visibleIcons by truncating the list to that amount.
35 // At the time of this writing, we cannot do that because looking up the SBIV can fail, and so
36 // we need additional icons to fall-back to.
37 /** Determines how a limit to the icons is to be applied. */
38 enum class LimitType {
39 /** The [Int] limit is a maximum amount of icons to be displayed. */
40 MaximumAmount,
41 /**
42 * The [Int] limit is a maximum index into the
43 * [list of visible icons][NotificationIconsViewData.visibleIcons] to be displayed; any
44 * icons beyond that index should be omitted.
45 */
46 MaximumIndex,
47 }
48
49 /** The difference between two [NotificationIconsViewData]s. */
50 data class Diff(
51 /** Icons added in the newer dataset. */
52 val added: List<String> = emptyList(),
53 /** Icons removed from the older dataset. */
54 val removed: List<String> = emptyList(),
55 /**
56 * Groups whose icon was replaced with a single new notification icon. The key of the [Map]
57 * is the notification group key, and the value is the new icon.
58 *
59 * Specifically, this models a difference where the older dataset had notification groups
60 * with a single icon in the set, and the newer dataset has a single, different icon for the
61 * same group. A view binder can use this information for special animations for this
62 * specific change.
63 */
64 val groupReplacements: Map<String, String> = emptyMap(),
65 )
66
67 companion object {
68 /**
69 * Returns an [NotificationIconsViewData.Diff] calculated from a [new] and [previous][prev]
70 * [NotificationIconsViewData] state.
71 */
72 fun computeDifference(
73 new: NotificationIconsViewData,
74 prev: NotificationIconsViewData
75 ): Diff {
76 val prevKeys = prev.visibleIcons.asSequence().map { it.notifKey }.toSet()
77 val newKeys = new.visibleIcons.asSequence().map { it.notifKey }.toSet()
78 val added: List<String> = newKeys.mapNotNull { key -> key.takeIf { it !in prevKeys } }
79 val removed: List<NotificationIconInfo> =
80 prev.visibleIcons.filter { it.notifKey !in newKeys }
81 val groupsToShow: Set<IconGroupInfo> =
82 new.visibleIcons.asSequence().map { it.groupInfo }.toSet()
83 val replacements: ArrayMap<String, String> =
84 removed
85 .asSequence()
86 .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
87 .groupBy { it.groupInfo.groupKey }
88 .mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
89 vs.takeIf { it.size == 1 }?.get(0)?.notifKey
90 }
91 return Diff(added, removed.map { it.notifKey }, replacements)
92 }
93 }
94 }
95
96 /** An Icon, and keys for unique identification. */
97 data class NotificationIconInfo(
98 val sourceIcon: Icon,
99 val notifKey: String,
100 val groupKey: String,
101 )
102
103 /**
104 * Construct an [NotificationIconInfo] out of an [ActiveNotificationModel], or return `null` if one
105 * cannot be created due to missing information.
106 */
ActiveNotificationModelnull107 fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): NotificationIconInfo? {
108 return sourceIcon?.let {
109 groupKey?.let { groupKey ->
110 NotificationIconInfo(
111 sourceIcon = sourceIcon,
112 notifKey = key,
113 groupKey = groupKey,
114 )
115 }
116 }
117 }
118
119 private val NotificationIconInfo.groupInfo: IconGroupInfo
120 get() = IconGroupInfo(sourceIcon, groupKey)
121
122 private data class IconGroupInfo(
123 val sourceIcon: Icon,
124 val groupKey: String,
125 ) {
126
equalsnull127 override fun equals(other: Any?): Boolean {
128 if (this === other) return true
129 if (javaClass != other?.javaClass) return false
130
131 other as IconGroupInfo
132
133 if (groupKey != other.groupKey) return false
134 return sourceIcon.sameAs(other.sourceIcon)
135 }
136
hashCodenull137 override fun hashCode(): Int {
138 var result = groupKey.hashCode()
139 result = 31 * result + sourceIcon.type.hashCode()
140 when (sourceIcon.type) {
141 Icon.TYPE_BITMAP,
142 Icon.TYPE_ADAPTIVE_BITMAP -> {
143 result = 31 * result + sourceIcon.bitmap.hashCode()
144 }
145 Icon.TYPE_DATA -> {
146 result = 31 * result + sourceIcon.dataLength.hashCode()
147 result = 31 * result + sourceIcon.dataOffset.hashCode()
148 }
149 Icon.TYPE_RESOURCE -> {
150 result = 31 * result + sourceIcon.resId.hashCode()
151 result = 31 * result + sourceIcon.resPackage.hashCode()
152 }
153 Icon.TYPE_URI,
154 Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
155 result = 31 * result + sourceIcon.uriString.hashCode()
156 }
157 }
158 return result
159 }
160 }
161