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