1 /* 2 * Copyright (C) 2014 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.server.notification; 17 18 import android.app.Notification; 19 import android.app.NotificationManager; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.telecom.TelecomManager; 25 26 import com.android.internal.util.NotificationMessagingUtil; 27 28 import java.util.Comparator; 29 import java.util.Objects; 30 31 /** 32 * Sorts notifications individually into attention-relevant order. 33 */ 34 public class NotificationComparator 35 implements Comparator<NotificationRecord> { 36 37 private final Context mContext; 38 private final NotificationMessagingUtil mMessagingUtil; 39 private String mDefaultPhoneApp; 40 41 public NotificationComparator(Context context) { 42 mContext = context; 43 mContext.registerReceiver(mPhoneAppBroadcastReceiver, 44 new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)); 45 mMessagingUtil = new NotificationMessagingUtil(mContext); 46 } 47 48 @Override 49 public int compare(NotificationRecord left, NotificationRecord right) { 50 // first all colorized notifications 51 boolean leftImportantColorized = isImportantColorized(left); 52 boolean rightImportantColorized = isImportantColorized(right); 53 54 if (leftImportantColorized != rightImportantColorized) { 55 return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized); 56 } 57 58 // sufficiently important ongoing notifications of certain categories 59 boolean leftImportantOngoing = isImportantOngoing(left); 60 boolean rightImportantOngoing = isImportantOngoing(right); 61 62 if (leftImportantOngoing != rightImportantOngoing) { 63 // by ongoing, ongoing higher than non-ongoing 64 return -1 * Boolean.compare(leftImportantOngoing, rightImportantOngoing); 65 } 66 67 boolean leftMessaging = isImportantMessaging(left); 68 boolean rightMessaging = isImportantMessaging(right); 69 if (leftMessaging != rightMessaging) { 70 return -1 * Boolean.compare(leftMessaging, rightMessaging); 71 } 72 73 // Next: sufficiently import person to person communication 74 boolean leftPeople = isImportantPeople(left); 75 boolean rightPeople = isImportantPeople(right); 76 final int contactAffinityComparison = 77 Float.compare(left.getContactAffinity(), right.getContactAffinity()); 78 79 if (leftPeople && rightPeople){ 80 // by contact proximity, close to far. if same proximity, check further fields. 81 if (contactAffinityComparison != 0) { 82 return -1 * contactAffinityComparison; 83 } 84 } else if (leftPeople != rightPeople) { 85 // People, messaging higher than non-messaging 86 return -1 * Boolean.compare(leftPeople, rightPeople); 87 } 88 89 final int leftImportance = left.getImportance(); 90 final int rightImportance = right.getImportance(); 91 if (leftImportance != rightImportance) { 92 // by importance, high to low 93 return -1 * Integer.compare(leftImportance, rightImportance); 94 } 95 96 // by contact proximity, close to far. if same proximity, check further fields. 97 if (contactAffinityComparison != 0) { 98 return -1 * contactAffinityComparison; 99 } 100 101 // Whether or not the notification can bypass DND. 102 final int leftPackagePriority = left.getPackagePriority(); 103 final int rightPackagePriority = right.getPackagePriority(); 104 if (leftPackagePriority != rightPackagePriority) { 105 // by priority, high to low 106 return -1 * Integer.compare(leftPackagePriority, rightPackagePriority); 107 } 108 109 final int leftPriority = left.sbn.getNotification().priority; 110 final int rightPriority = right.sbn.getNotification().priority; 111 if (leftPriority != rightPriority) { 112 // by priority, high to low 113 return -1 * Integer.compare(leftPriority, rightPriority); 114 } 115 116 // then break ties by time, most recent first 117 return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs()); 118 } 119 120 private boolean isImportantColorized(NotificationRecord record) { 121 if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { 122 return false; 123 } 124 return record.getNotification().isColorized(); 125 } 126 127 private boolean isImportantOngoing(NotificationRecord record) { 128 if (!isOngoing(record)) { 129 return false; 130 } 131 132 if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { 133 return false; 134 } 135 136 return isCall(record) || isMediaNotification(record); 137 } 138 139 protected boolean isImportantPeople(NotificationRecord record) { 140 if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { 141 return false; 142 } 143 if (record.getContactAffinity() > ValidateNotificationPeople.NONE) { 144 return true; 145 } 146 return false; 147 } 148 149 protected boolean isImportantMessaging(NotificationRecord record) { 150 return mMessagingUtil.isImportantMessaging(record.sbn, record.getImportance()); 151 } 152 153 private boolean isOngoing(NotificationRecord record) { 154 final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE; 155 return (record.getNotification().flags & ongoingFlags) != 0; 156 } 157 158 private boolean isMediaNotification(NotificationRecord record) { 159 return record.getNotification().hasMediaSession(); 160 } 161 162 private boolean isCall(NotificationRecord record) { 163 return record.isCategory(Notification.CATEGORY_CALL) 164 && isDefaultPhoneApp(record.sbn.getPackageName()); 165 } 166 167 private boolean isDefaultPhoneApp(String pkg) { 168 if (mDefaultPhoneApp == null) { 169 final TelecomManager telecomm = 170 (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); 171 mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultDialerPackage() : null; 172 } 173 return Objects.equals(pkg, mDefaultPhoneApp); 174 } 175 176 private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() { 177 @Override 178 public void onReceive(Context context, Intent intent) { 179 mDefaultPhoneApp = 180 intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME); 181 } 182 }; 183 } 184