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 */
16package com.android.server.notification;
17
18import android.app.Notification;
19import android.app.NotificationManager;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.telecom.TelecomManager;
25
26import com.android.internal.util.NotificationMessagingUtil;
27
28import java.util.Comparator;
29import java.util.Objects;
30
31/**
32 * Sorts notifications individually into attention-relevant order.
33 */
34public 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.getNotification().category == 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