/*
|
* Copyright (C) 2014 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.server.notification;
|
|
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
|
import android.app.Notification;
|
import android.app.NotificationManager;
|
import android.content.BroadcastReceiver;
|
import android.content.Context;
|
import android.content.Intent;
|
import android.content.IntentFilter;
|
import android.provider.Settings;
|
import android.telecom.TelecomManager;
|
|
import com.android.internal.util.NotificationMessagingUtil;
|
|
import java.util.Comparator;
|
import java.util.Objects;
|
|
/**
|
* Sorts notifications individually into attention-relevant order.
|
*/
|
public class NotificationComparator
|
implements Comparator<NotificationRecord> {
|
|
private final Context mContext;
|
private final NotificationMessagingUtil mMessagingUtil;
|
private String mDefaultPhoneApp;
|
|
public NotificationComparator(Context context) {
|
mContext = context;
|
mContext.registerReceiver(mPhoneAppBroadcastReceiver,
|
new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
|
mMessagingUtil = new NotificationMessagingUtil(mContext);
|
}
|
|
@Override
|
public int compare(NotificationRecord left, NotificationRecord right) {
|
final int leftImportance = left.getImportance();
|
final int rightImportance = right.getImportance();
|
final boolean isLeftHighImportance = leftImportance >= IMPORTANCE_DEFAULT;
|
final boolean isRightHighImportance = rightImportance >= IMPORTANCE_DEFAULT;
|
|
// With new interruption model, prefer importance bucket above all other criteria
|
// (to ensure buckets are contiguous)
|
if (Settings.Secure.getInt(mContext.getContentResolver(),
|
Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) {
|
if (isLeftHighImportance != isRightHighImportance) {
|
// by importance bucket, high importance higher than low importance
|
return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance);
|
}
|
}
|
|
// first all colorized notifications
|
boolean leftImportantColorized = isImportantColorized(left);
|
boolean rightImportantColorized = isImportantColorized(right);
|
|
if (leftImportantColorized != rightImportantColorized) {
|
return -1 * Boolean.compare(leftImportantColorized, rightImportantColorized);
|
}
|
|
// sufficiently important ongoing notifications of certain categories
|
boolean leftImportantOngoing = isImportantOngoing(left);
|
boolean rightImportantOngoing = isImportantOngoing(right);
|
|
if (leftImportantOngoing != rightImportantOngoing) {
|
// by ongoing, ongoing higher than non-ongoing
|
return -1 * Boolean.compare(leftImportantOngoing, rightImportantOngoing);
|
}
|
|
boolean leftMessaging = isImportantMessaging(left);
|
boolean rightMessaging = isImportantMessaging(right);
|
if (leftMessaging != rightMessaging) {
|
return -1 * Boolean.compare(leftMessaging, rightMessaging);
|
}
|
|
// Next: sufficiently import person to person communication
|
boolean leftPeople = isImportantPeople(left);
|
boolean rightPeople = isImportantPeople(right);
|
final int contactAffinityComparison =
|
Float.compare(left.getContactAffinity(), right.getContactAffinity());
|
|
if (leftPeople && rightPeople){
|
// by contact proximity, close to far. if same proximity, check further fields.
|
if (contactAffinityComparison != 0) {
|
return -1 * contactAffinityComparison;
|
}
|
} else if (leftPeople != rightPeople) {
|
// People, messaging higher than non-messaging
|
return -1 * Boolean.compare(leftPeople, rightPeople);
|
}
|
|
if (leftImportance != rightImportance) {
|
// by importance, high to low
|
return -1 * Integer.compare(leftImportance, rightImportance);
|
}
|
|
// by contact proximity, close to far. if same proximity, check further fields.
|
if (contactAffinityComparison != 0) {
|
return -1 * contactAffinityComparison;
|
}
|
|
// Whether or not the notification can bypass DND.
|
final int leftPackagePriority = left.getPackagePriority();
|
final int rightPackagePriority = right.getPackagePriority();
|
if (leftPackagePriority != rightPackagePriority) {
|
// by priority, high to low
|
return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
|
}
|
|
final int leftPriority = left.sbn.getNotification().priority;
|
final int rightPriority = right.sbn.getNotification().priority;
|
if (leftPriority != rightPriority) {
|
// by priority, high to low
|
return -1 * Integer.compare(leftPriority, rightPriority);
|
}
|
|
// then break ties by time, most recent first
|
return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
|
}
|
|
private boolean isImportantColorized(NotificationRecord record) {
|
if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
|
return false;
|
}
|
return record.getNotification().isColorized();
|
}
|
|
private boolean isImportantOngoing(NotificationRecord record) {
|
if (!isOngoing(record)) {
|
return false;
|
}
|
|
if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
|
return false;
|
}
|
|
return isCall(record) || isMediaNotification(record);
|
}
|
|
protected boolean isImportantPeople(NotificationRecord record) {
|
if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) {
|
return false;
|
}
|
if (record.getContactAffinity() > ValidateNotificationPeople.NONE) {
|
return true;
|
}
|
return false;
|
}
|
|
protected boolean isImportantMessaging(NotificationRecord record) {
|
return mMessagingUtil.isImportantMessaging(record.sbn, record.getImportance());
|
}
|
|
private boolean isOngoing(NotificationRecord record) {
|
final int ongoingFlags = Notification.FLAG_FOREGROUND_SERVICE;
|
return (record.getNotification().flags & ongoingFlags) != 0;
|
}
|
|
private boolean isMediaNotification(NotificationRecord record) {
|
return record.getNotification().hasMediaSession();
|
}
|
|
private boolean isCall(NotificationRecord record) {
|
return record.isCategory(Notification.CATEGORY_CALL)
|
&& isDefaultPhoneApp(record.sbn.getPackageName());
|
}
|
|
private boolean isDefaultPhoneApp(String pkg) {
|
if (mDefaultPhoneApp == null) {
|
final TelecomManager telecomm =
|
(TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
|
mDefaultPhoneApp = telecomm != null ? telecomm.getDefaultDialerPackage() : null;
|
}
|
return Objects.equals(pkg, mDefaultPhoneApp);
|
}
|
|
private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
|
@Override
|
public void onReceive(Context context, Intent intent) {
|
mDefaultPhoneApp =
|
intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
|
}
|
};
|
}
|