• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.collection.coordinator;
18 
19 import static android.app.NotificationManager.IMPORTANCE_MIN;
20 
21 import android.app.Notification;
22 
23 import androidx.annotation.NonNull;
24 import androidx.annotation.Nullable;
25 
26 import com.android.systemui.dagger.qualifiers.Application;
27 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
28 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
29 import com.android.systemui.statusbar.notification.collection.PipelineEntry;
30 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
31 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
32 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
33 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
34 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
35 import com.android.systemui.statusbar.notification.promoted.domain.interactor.PromotedNotificationsInteractor;
36 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
37 import com.android.systemui.util.kotlin.JavaAdapterKt;
38 
39 import kotlinx.coroutines.CoroutineScope;
40 
41 import java.util.Collections;
42 import java.util.List;
43 
44 import javax.inject.Inject;
45 
46 /**
47  * Handles sectioning for foreground service notifications.
48  * Puts non-min colorized foreground service notifications into the FGS section. See
49  * {@link NotifCoordinators} for section ordering priority.
50  */
51 @CoordinatorScope
52 public class ColorizedFgsCoordinator implements Coordinator {
53     private static final String TAG = "ColorizedCoordinator";
54     private final PromotedNotificationsInteractor mPromotedNotificationsInteractor;
55     private final CoroutineScope mMainScope;
56 
57     private List<String> mOrderedPromotedNotifKeys = Collections.emptyList();
58 
59     @Inject
ColorizedFgsCoordinator( @pplication CoroutineScope mainScope, PromotedNotificationsInteractor promotedNotificationsInteractor )60     public ColorizedFgsCoordinator(
61             @Application CoroutineScope mainScope,
62             PromotedNotificationsInteractor promotedNotificationsInteractor
63     ) {
64         mPromotedNotificationsInteractor = promotedNotificationsInteractor;
65         mMainScope = mainScope;
66     }
67 
68     @Override
attach(@onNull NotifPipeline pipeline)69     public void attach(@NonNull NotifPipeline pipeline) {
70         if (PromotedNotificationUi.isEnabled()) {
71             pipeline.addPromoter(mPromotedOngoingPromoter);
72 
73             JavaAdapterKt.collectFlow(mMainScope,
74                     mPromotedNotificationsInteractor.getOrderedChipNotificationKeys(),
75                     (List<String> keys) -> {
76                         mOrderedPromotedNotifKeys = keys;
77                         mNotifSectioner.invalidateList("updated mOrderedPromotedNotifKeys");
78                     });
79         }
80     }
81 
getSectioner()82     public NotifSectioner getSectioner() {
83         return mNotifSectioner;
84     }
85 
86     private final NotifPromoter mPromotedOngoingPromoter = new NotifPromoter("PromotedOngoing") {
87         @Override
88         public boolean shouldPromoteToTopLevel(NotificationEntry child) {
89             return isPromotedOngoing(child);
90         }
91     };
92 
93     /**
94      * Puts colorized foreground service and call notifications into its own section.
95      */
96     private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
97             NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
98         @Override
99         public boolean isInSection(PipelineEntry entry) {
100             NotificationEntry notifEntry = entry.getRepresentativeEntry();
101             if (notifEntry == null) {
102                 return false;
103             }
104             if (BundleUtil.Companion.isClassified(notifEntry)) {
105                 return false;
106             }
107             return isRichOngoing(notifEntry) || isPromotedNotifChip(notifEntry);
108         }
109 
110         /** get the sort key for any entry in the ongoing section */
111         private int getSortKey(@Nullable NotificationEntry entry) {
112             if (entry == null) return Integer.MAX_VALUE;
113             // Order all promoted notif keys first, using their order in the list
114             final int index = mOrderedPromotedNotifKeys.indexOf(entry.getKey());
115             if (index >= 0) return index;
116             // Next, prioritize promoted ongoing over other notifications
117             return isPromotedOngoing(entry) ? Integer.MAX_VALUE - 1 : Integer.MAX_VALUE;
118         }
119 
120         private final NotifComparator mOngoingComparator = new NotifComparator(
121                 "OngoingComparator") {
122             @Override
123             public int compare(@NonNull PipelineEntry o1, @NonNull PipelineEntry o2) {
124                 return Integer.compare(
125                         getSortKey(o1.getRepresentativeEntry()),
126                         getSortKey(o2.getRepresentativeEntry())
127                 );
128             }
129         };
130 
131         @Nullable
132         @Override
133         public NotifComparator getComparator() {
134             if (PromotedNotificationUi.isEnabled()) {
135                 return mOngoingComparator;
136             } else {
137                 return null;
138             }
139         }
140     };
141 
142     /** Determines if the given notification is a colorized or call notification */
isRichOngoing(NotificationEntry entry)143     public static boolean isRichOngoing(NotificationEntry entry) {
144         return isPromotedOngoing(entry) || isColorizedForegroundService(entry) || isCall(entry);
145     }
146 
isColorizedForegroundService(NotificationEntry entry)147     private static boolean isColorizedForegroundService(NotificationEntry entry) {
148         Notification notification = entry.getSbn().getNotification();
149         return notification.isForegroundService()
150                 && notification.isColorized()
151                 && entry.getImportance() > IMPORTANCE_MIN;
152     }
153 
isPromotedOngoing(NotificationEntry entry)154     private static boolean isPromotedOngoing(NotificationEntry entry) {
155         // NOTE: isPromotedOngoing already checks the android.app.ui_rich_ongoing flag.
156         return entry != null && entry.getSbn().getNotification().isPromotedOngoing();
157     }
158 
isCall(NotificationEntry entry)159     private static boolean isCall(NotificationEntry entry) {
160         Notification notification = entry.getSbn().getNotification();
161         return entry.getImportance() > IMPORTANCE_MIN
162                 && notification.isStyle(Notification.CallStyle.class);
163     }
164 
isPromotedNotifChip(NotificationEntry entry)165     private boolean isPromotedNotifChip(NotificationEntry entry) {
166         return PromotedNotificationUi.isEnabled()
167                 && entry.getImportance() > IMPORTANCE_MIN
168                 && mOrderedPromotedNotifKeys.contains(entry.getKey());
169     }
170 }
171