1 /* 2 * Copyright (C) 2018 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.row; 18 19 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; 20 21 import android.content.Context; 22 import android.metrics.LogMaker; 23 import android.util.Log; 24 25 import androidx.annotation.VisibleForTesting; 26 27 import com.android.internal.logging.MetricsLogger; 28 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 29 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; 30 import com.android.systemui.statusbar.notification.NotificationEntryManager; 31 import com.android.systemui.statusbar.notification.dagger.NotificationsModule; 32 import com.android.systemui.statusbar.notification.logging.NotificationCounters; 33 34 import java.util.Collections; 35 import java.util.HashSet; 36 import java.util.Set; 37 38 /** 39 * Manager for the notification blocking helper - tracks and helps create the blocking helper 40 * affordance. 41 */ 42 public class NotificationBlockingHelperManager { 43 /** Enables debug logging and always makes the blocking helper show up after a dismiss. */ 44 private static final boolean DEBUG = false; 45 private static final String TAG = "BlockingHelper"; 46 47 private final Context mContext; 48 private final NotificationGutsManager mNotificationGutsManager; 49 private final NotificationEntryManager mNotificationEntryManager; 50 private final MetricsLogger mMetricsLogger; 51 /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */ 52 private ExpandableNotificationRow mBlockingHelperRow; 53 private Set<String> mNonBlockablePkgs; 54 55 /** 56 * Whether the notification shade/stack is expanded - used to determine blocking helper 57 * eligibility. 58 */ 59 private boolean mIsShadeExpanded; 60 61 /** 62 * Injected constructor. See {@link NotificationsModule}. 63 */ NotificationBlockingHelperManager( Context context, NotificationGutsManager notificationGutsManager, NotificationEntryManager notificationEntryManager, MetricsLogger metricsLogger)64 public NotificationBlockingHelperManager( 65 Context context, 66 NotificationGutsManager notificationGutsManager, 67 NotificationEntryManager notificationEntryManager, 68 MetricsLogger metricsLogger) { 69 mContext = context; 70 mNotificationGutsManager = notificationGutsManager; 71 mNotificationEntryManager = notificationEntryManager; 72 mMetricsLogger = metricsLogger; 73 mNonBlockablePkgs = new HashSet<>(); 74 Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray( 75 com.android.internal.R.array.config_nonBlockableNotificationPackages)); 76 } 77 78 /** 79 * Potentially shows the blocking helper, represented via the {@link NotificationInfo} menu 80 * item, in the current row if user sentiment is negative. 81 * 82 * @param row row to render the blocking helper in 83 * @param menuRow menu used to generate the {@link NotificationInfo} view that houses the 84 * blocking helper UI 85 * @return whether we're showing a blocking helper in the given notification row 86 */ perhapsShowBlockingHelper( ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow)87 boolean perhapsShowBlockingHelper( 88 ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow) { 89 // We only show the blocking helper if: 90 // - User sentiment is negative (DEBUG flag can bypass) 91 // - The notification shade is fully expanded (guarantees we're not touching a HUN). 92 // - The row is blockable (i.e. not non-blockable) 93 // - The dismissed row is a valid group (>1 or 0 children from the same channel) 94 // or the only child in the group 95 if ((row.getEntry().getUserSentiment() == USER_SENTIMENT_NEGATIVE || DEBUG) 96 && mIsShadeExpanded 97 && !row.getIsNonblockable() 98 && ((!row.isChildInGroup() || row.isOnlyChildInGroup()) 99 && row.getNumUniqueChannels() <= 1)) { 100 // Dismiss any current blocking helper before continuing forward (only one can be shown 101 // at a given time). 102 dismissCurrentBlockingHelper(); 103 104 if (DEBUG) { 105 Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper"); 106 } 107 108 // Enable blocking helper on the row before moving forward so everything in the guts is 109 // correctly prepped. 110 mBlockingHelperRow = row; 111 mBlockingHelperRow.setBlockingHelperShowing(true); 112 113 // Log triggering of blocking helper by the system. This log line 114 // should be emitted before the "display" log line. 115 mMetricsLogger.write( 116 getLogMaker().setSubtype(MetricsEvent.BLOCKING_HELPER_TRIGGERED_BY_SYSTEM)); 117 118 // We don't care about the touch origin (x, y) since we're opening guts without any 119 // explicit user interaction. 120 mNotificationGutsManager.openGuts( 121 mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext)); 122 123 mMetricsLogger.count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1); 124 return true; 125 } 126 return false; 127 } 128 129 /** 130 * Dismiss the currently showing blocking helper, if any, through a notification update. 131 * 132 * @return whether the blocking helper was dismissed 133 */ dismissCurrentBlockingHelper()134 boolean dismissCurrentBlockingHelper() { 135 if (!isBlockingHelperRowNull()) { 136 if (DEBUG) { 137 Log.d(TAG, "Manager.dismissCurrentBlockingHelper: Dismissing current helper"); 138 } 139 if (!mBlockingHelperRow.isBlockingHelperShowing()) { 140 Log.e(TAG, "Manager.dismissCurrentBlockingHelper: " 141 + "Non-null row is not showing a blocking helper"); 142 } 143 144 mBlockingHelperRow.setBlockingHelperShowing(false); 145 if (mBlockingHelperRow.isAttachedToWindow()) { 146 mNotificationEntryManager.updateNotifications("dismissCurrentBlockingHelper"); 147 } 148 mBlockingHelperRow = null; 149 return true; 150 } 151 return false; 152 } 153 154 /** 155 * Update the expansion status of the notification shade/stack. 156 * 157 * @param expandedHeight how much the shade is expanded ({code 0} indicating it's collapsed) 158 */ setNotificationShadeExpanded(float expandedHeight)159 public void setNotificationShadeExpanded(float expandedHeight) { 160 mIsShadeExpanded = expandedHeight > 0.0f; 161 } 162 163 /** 164 * Returns whether the given package name is in the list of non-blockable packages. 165 */ isNonblockable(String packageName, String channelName)166 public boolean isNonblockable(String packageName, String channelName) { 167 return mNonBlockablePkgs.contains(packageName) 168 || mNonBlockablePkgs.contains(makeChannelKey(packageName, channelName)); 169 } 170 getLogMaker()171 private LogMaker getLogMaker() { 172 return mBlockingHelperRow.getEntry().getSbn() 173 .getLogMaker() 174 .setCategory(MetricsEvent.NOTIFICATION_BLOCKING_HELPER); 175 } 176 177 // Format must stay in sync with frameworks/base/core/res/res/values/config.xml 178 // config_nonBlockableNotificationPackages makeChannelKey(String pkg, String channel)179 private String makeChannelKey(String pkg, String channel) { 180 return pkg + ":" + channel; 181 } 182 183 @VisibleForTesting isBlockingHelperRowNull()184 boolean isBlockingHelperRowNull() { 185 return mBlockingHelperRow == null; 186 } 187 188 @VisibleForTesting setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest)189 void setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest) { 190 mBlockingHelperRow = blockingHelperRowForTest; 191 } 192 193 @VisibleForTesting setNonBlockablePkgs(String[] pkgsAndChannels)194 void setNonBlockablePkgs(String[] pkgsAndChannels) { 195 mNonBlockablePkgs = new HashSet<>(); 196 Collections.addAll(mNonBlockablePkgs, pkgsAndChannels); 197 } 198 } 199