1 /*
2  * Copyright (C) 2019 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.people
18 
19 import android.annotation.IntDef
20 import android.service.notification.NotificationListenerService.Ranking
21 import android.service.notification.StatusBarNotification
22 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
23 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
24 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
25 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
26 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
27 import com.android.systemui.statusbar.phone.NotificationGroupManager
28 import javax.inject.Inject
29 import javax.inject.Singleton
30 import kotlin.math.max
31 
32 interface PeopleNotificationIdentifier {
33 
34     /**
35      * Identifies if the given notification can be classified as a "People" notification.
36      *
37      * @return [TYPE_NON_PERSON] if not a people notification, [TYPE_PERSON] if it is a people
38      *  notification that doesn't use shortcuts, [TYPE_FULL_PERSON] if it is a person notification
39      *  that users shortcuts, and [TYPE_IMPORTANT_PERSON] if an "important" people notification
40      *  that users shortcuts.
41      */
42     @PeopleNotificationType
getPeopleNotificationTypenull43     fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int
44 
45     fun compareTo(@PeopleNotificationType a: Int,
46                   @PeopleNotificationType b: Int): Int
47 
48     companion object {
49 
50         @Retention(AnnotationRetention.SOURCE)
51         @IntDef(prefix = ["TYPE_"], value = [TYPE_NON_PERSON, TYPE_PERSON, TYPE_FULL_PERSON,
52             TYPE_IMPORTANT_PERSON])
53         annotation class PeopleNotificationType
54 
55         const val TYPE_NON_PERSON = 0
56         const val TYPE_PERSON = 1
57         const val TYPE_FULL_PERSON = 2
58         const val TYPE_IMPORTANT_PERSON = 3
59     }
60 }
61 
62 @Singleton
63 class PeopleNotificationIdentifierImpl @Inject constructor(
64     private val personExtractor: NotificationPersonExtractor,
65     private val groupManager: NotificationGroupManager
66 ) : PeopleNotificationIdentifier {
67 
68     @PeopleNotificationType
getPeopleNotificationTypenull69     override fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int =
70             when (val type = ranking.personTypeInfo) {
71                 TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
72                 else -> {
73                     when (val type = upperBound(type, extractPersonTypeInfo(sbn))) {
74                         TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
75                         else -> upperBound(type, getPeopleTypeOfSummary(sbn))
76                     }
77                 }
78             }
79 
compareTonull80     override fun compareTo(@PeopleNotificationType a: Int,
81                   @PeopleNotificationType b: Int): Int {
82         return b.compareTo(a);
83     }
84 
85     /**
86      * Given two [PeopleNotificationType]s, determine the upper bound. Used to constrain a
87      * notification to a type given multiple signals, i.e. notification groups, where each child
88      * has a [PeopleNotificationType] that is used to constrain the summary.
89      */
90     @PeopleNotificationType
upperBoundnull91     private fun upperBound(
92         @PeopleNotificationType type: Int,
93         @PeopleNotificationType other: Int
94     ): Int =
95             max(type, other)
96 
97     private val Ranking.personTypeInfo
98         get() = when {
99             !isConversation -> TYPE_NON_PERSON
100             shortcutInfo == null -> TYPE_PERSON
101             channel?.isImportantConversation == true -> TYPE_IMPORTANT_PERSON
102             else -> TYPE_FULL_PERSON
103         }
104 
extractPersonTypeInfonull105     private fun extractPersonTypeInfo(sbn: StatusBarNotification) =
106             if (personExtractor.isPersonNotification(sbn)) TYPE_PERSON else TYPE_NON_PERSON
107 
108     private fun getPeopleTypeOfSummary(statusBarNotification: StatusBarNotification): Int {
109         if (!groupManager.isSummaryOfGroup(statusBarNotification)) {
110             return TYPE_NON_PERSON
111         }
112 
113         val childTypes = groupManager.getChildren(statusBarNotification)
114                 ?.asSequence()
115                 ?.map { getPeopleNotificationType(it.sbn, it.ranking) }
116                 ?: return TYPE_NON_PERSON
117 
118         var groupType = TYPE_NON_PERSON
119         for (childType in childTypes) {
120             groupType = upperBound(groupType, childType)
121             if (groupType == TYPE_IMPORTANT_PERSON)
122                 break
123         }
124         return groupType
125     }
126 }
127