1 /*
2  * Copyright 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.notification.collection.inflation
18 
19 import android.app.Notification
20 import android.app.RemoteInput
21 import android.graphics.drawable.Icon
22 import android.text.TextUtils
23 
24 /**
25  * An immutable object which contains minimal state extracted from an entry that represents state
26  * which can change without a direct app update (e.g. with a ranking update).
27  * Diffing two entries determines if view re-inflation is needed.
28  */
29 class NotifUiAdjustment internal constructor(
30     val key: String,
31     val smartActions: List<Notification.Action>,
32     val smartReplies: List<CharSequence>,
33     val isConversation: Boolean,
34     val isSnoozeEnabled: Boolean,
35     val isMinimized: Boolean,
36     val needsRedaction: Boolean,
37 ) {
38     companion object {
39         @JvmStatic
40         fun needReinflate(
41             oldAdjustment: NotifUiAdjustment,
42             newAdjustment: NotifUiAdjustment
43         ): Boolean = when {
44             oldAdjustment === newAdjustment -> false
45             oldAdjustment.isConversation != newAdjustment.isConversation -> true
46             oldAdjustment.isSnoozeEnabled != newAdjustment.isSnoozeEnabled -> true
47             oldAdjustment.isMinimized != newAdjustment.isMinimized -> true
48             oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
49             areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
50             newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
51             else -> false
52         }
53 
54         private fun areDifferent(
55             first: List<Notification.Action>,
56             second: List<Notification.Action>
57         ): Boolean = when {
58             first === second -> false
59             first.size != second.size -> true
60             else -> first.asSequence().zip(second.asSequence()).any {
61                 (!TextUtils.equals(it.first.title, it.second.title)) ||
62                     (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
63                     (it.first.actionIntent != it.second.actionIntent) ||
64                     (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
65             }
66         }
67 
68         private fun areDifferent(first: Icon?, second: Icon?): Boolean = when {
69             first === second -> false
70             first == null || second == null -> true
71             else -> !first.sameAs(second)
72         }
73 
74         private fun areDifferent(
75             first: Array<RemoteInput>?,
76             second: Array<RemoteInput>?
77         ): Boolean = when {
78             first === second -> false
79             first == null || second == null -> true
80             first.size != second.size -> true
81             else -> first.asSequence().zip(second.asSequence()).any {
82                 (!TextUtils.equals(it.first.label, it.second.label)) ||
83                     (areDifferent(it.first.choices, it.second.choices))
84             }
85         }
86 
87         private fun areDifferent(
88             first: Array<CharSequence>?,
89             second: Array<CharSequence>?
90         ): Boolean = when {
91             first === second -> false
92             first == null || second == null -> true
93             first.size != second.size -> true
94             else -> first.asSequence().zip(second.asSequence()).any {
95                 !TextUtils.equals(it.first, it.second)
96             }
97         }
98     }
99 }
100