1 /* 2 * Copyright (C) 2018 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.car.notification; 17 18 import android.app.Notification; 19 import android.content.Context; 20 import android.os.Bundle; 21 22 import androidx.annotation.NonNull; 23 import androidx.recyclerview.widget.DiffUtil; 24 25 import java.util.List; 26 import java.util.Objects; 27 28 /** 29 * {@link DiffUtil} for car notifications. 30 * This class is not intended for general usage except for the static methods. 31 * 32 * <p> Two notifications are considered the same if they have the same: 33 * <ol> 34 * <li> GroupKey 35 * <li> Number of AlertEntry contained 36 * <li> The order of each AlertEntry 37 * <li> The identifier of each individual AlertEntry contained 38 * <li> The content of each individual AlertEntry contained 39 * </ol> 40 */ 41 class CarNotificationDiff extends DiffUtil.Callback { 42 private final Context mContext; 43 private final List<NotificationGroup> mOldList; 44 private final List<NotificationGroup> mNewList; 45 CarNotificationDiff( Context context, @NonNull List<NotificationGroup> oldList, @NonNull List<NotificationGroup> newList)46 CarNotificationDiff( 47 Context context, 48 @NonNull 49 List<NotificationGroup> oldList, 50 @NonNull 51 List<NotificationGroup> newList) { 52 mContext = context; 53 mOldList = oldList; 54 mNewList = newList; 55 } 56 57 @Override getOldListSize()58 public int getOldListSize() { 59 return mOldList.size(); 60 } 61 62 @Override getNewListSize()63 public int getNewListSize() { 64 return mNewList.size(); 65 } 66 67 @Override areItemsTheSame(int oldItemPosition, int newItemPosition)68 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 69 NotificationGroup oldItem = mOldList.get(oldItemPosition); 70 NotificationGroup newItem = mNewList.get(newItemPosition); 71 72 return sameGroupUniqueIdentifiers(oldItem, newItem); 73 } 74 75 /** 76 * Shallow comparison for {@link NotificationGroup}. 77 * <p> 78 * Checks if two grouped notifications have the same: 79 * <ol> 80 * <li> GroupKey 81 * <li> GroupSummaryKey 82 * </ol> 83 * <p> 84 * This method does not check for child AlertEntries because child itself will take care of it. 85 */ sameGroupUniqueIdentifiers(NotificationGroup oldItem, NotificationGroup newItem)86 static boolean sameGroupUniqueIdentifiers(NotificationGroup oldItem, 87 NotificationGroup newItem) { 88 89 if (oldItem == newItem) { 90 return true; 91 } 92 93 if (!oldItem.getGroupKey().equals(newItem.getGroupKey())) { 94 return false; 95 } 96 97 return sameNotificationKey( 98 oldItem.getGroupSummaryNotification(), newItem.getGroupSummaryNotification()); 99 } 100 101 /** 102 * Shallow comparison for {@link AlertEntry}: only comparing the unique IDs. 103 * 104 * <p> Returns true if two notifications have the same key. 105 */ sameNotificationKey(AlertEntry oldItem, AlertEntry newItem)106 static boolean sameNotificationKey(AlertEntry oldItem, AlertEntry newItem) { 107 if (oldItem == newItem) { 108 return true; 109 } 110 111 return oldItem != null 112 && newItem != null 113 && Objects.equals(oldItem.getKey(), newItem.getKey()); 114 } 115 116 /** 117 * Shallow comparison for {@link AlertEntry}: comparing the unique IDs and the 118 * notification Flags. 119 * 120 * <p> Returns true if two notifications have the same key and notification flags. 121 */ sameNotificationKeyAndFlags(AlertEntry oldItem, AlertEntry newItem)122 static boolean sameNotificationKeyAndFlags(AlertEntry oldItem, AlertEntry newItem) { 123 return sameNotificationKey(oldItem, newItem) 124 && oldItem.getNotification().flags == newItem.getNotification().flags; 125 } 126 127 /** 128 * Deep comparison for {@link NotificationGroup}. 129 * 130 * <p> Compare the size and contents of each AlertEntry inside the NotificationGroup. 131 * 132 * <p> This method will only be called if {@link #areItemsTheSame} returns true. 133 */ 134 @Override areContentsTheSame(int oldItemPosition, int newItemPosition)135 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 136 NotificationGroup oldItem = mOldList.get(oldItemPosition); 137 NotificationGroup newItem = mNewList.get(newItemPosition); 138 139 // Header and Footer should always refresh if some notification items have changed. 140 if (newItem.isHeader() || newItem.isFooter()) { 141 return false; 142 } 143 144 if (!sameNotificationContent( 145 oldItem.getGroupSummaryNotification(), newItem.getGroupSummaryNotification())) { 146 return false; 147 } 148 149 if (oldItem.getChildCount() != newItem.getChildCount()) { 150 return false; 151 } 152 153 List<AlertEntry> oldChildNotifications = oldItem.getChildNotifications(); 154 List<AlertEntry> newChildNotifications = newItem.getChildNotifications(); 155 156 for (int i = 0; i < oldItem.getChildCount(); i++) { 157 AlertEntry oldNotification = oldChildNotifications.get(i); 158 AlertEntry newNotification = newChildNotifications.get(i); 159 if (!sameNotificationContent(oldNotification, newNotification)) { 160 return false; 161 } 162 } 163 164 return true; 165 } 166 167 /** 168 * Deep comparison for {@link AlertEntry}. 169 * 170 * <p> We are only comparing a subset of the fields that have visible effects on our product. 171 * Most of the deprecated fields are not compared. 172 * Fields that do not have visible effects (e.g. privacy-related) are ignored for now. 173 */ sameNotificationContent(AlertEntry oldItem, AlertEntry newItem)174 private boolean sameNotificationContent(AlertEntry oldItem, AlertEntry newItem) { 175 176 if (oldItem == newItem) { 177 return true; 178 } 179 180 if (oldItem == null || newItem == null) { 181 return false; 182 } 183 184 if (oldItem.getStatusBarNotification().isGroup() 185 != newItem.getStatusBarNotification().isGroup() 186 || oldItem.getStatusBarNotification().isClearable() 187 != newItem.getStatusBarNotification().isClearable() 188 || oldItem.getStatusBarNotification().isOngoing() 189 != newItem.getStatusBarNotification().isOngoing()) { 190 return false; 191 } 192 193 Notification oldNotification = oldItem.getNotification(); 194 Notification newNotification = newItem.getNotification(); 195 196 if (oldNotification.flags != newNotification.flags 197 || oldNotification.category != newNotification.category 198 || oldNotification.color != newNotification.color 199 || !areBundlesEqual(oldNotification.extras, newNotification.extras) 200 || !Objects.equals(oldNotification.contentIntent, newNotification.contentIntent) 201 || !Objects.equals(oldNotification.deleteIntent, newNotification.deleteIntent) 202 || !Objects.equals( 203 oldNotification.fullScreenIntent, newNotification.fullScreenIntent) 204 || !Objects.deepEquals(oldNotification.actions, newNotification.actions)) { 205 return false; 206 } 207 208 // Recover builders only until the above if-statements fail 209 Notification.Builder oldBuilder = 210 Notification.Builder.recoverBuilder(mContext, oldNotification); 211 Notification.Builder newBuilder = 212 Notification.Builder.recoverBuilder(mContext, newNotification); 213 214 return !Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder); 215 } 216 areBundlesEqual(Bundle oldBundle, Bundle newBundle)217 private boolean areBundlesEqual(Bundle oldBundle, Bundle newBundle) { 218 if (oldBundle.size() != newBundle.size()) { 219 return false; 220 } 221 222 for (String key : oldBundle.keySet()) { 223 if (!newBundle.containsKey(key)) { 224 return false; 225 } 226 227 Object oldValue = oldBundle.get(key); 228 Object newValue = newBundle.get(key); 229 if (!Objects.equals(oldValue, newValue)) { 230 return false; 231 } 232 } 233 234 return true; 235 } 236 } 237