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;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager;
21 import android.os.UserHandle;
22 import android.service.notification.StatusBarNotification;
23 import android.support.annotation.VisibleForTesting;
24 import android.util.Log;
25 
26 import com.android.internal.logging.MetricsLogger;
27 import com.android.systemui.Dependency;
28 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
29 import com.android.systemui.statusbar.notification.NotificationCounters;
30 import com.android.systemui.statusbar.phone.StatusBar;
31 
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.Set;
35 
36 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
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     /** Row that the blocking helper will be shown in (via {@link NotificationGuts}. */
49     private ExpandableNotificationRow mBlockingHelperRow;
50     private Set<String> mNonBlockablePkgs;
51 
52     /**
53      * Whether the notification shade/stack is expanded - used to determine blocking helper
54      * eligibility.
55      */
56     private boolean mIsShadeExpanded;
57 
NotificationBlockingHelperManager(Context context)58     public NotificationBlockingHelperManager(Context context) {
59         mContext = context;
60         mNonBlockablePkgs = new HashSet<>();
61         Collections.addAll(mNonBlockablePkgs, mContext.getResources().getStringArray(
62                 com.android.internal.R.array.config_nonBlockableNotificationPackages));
63     }
64 
65     /**
66      * Potentially shows the blocking helper, represented via the {@link NotificationInfo} menu
67      * item, in the current row if user sentiment is negative.
68      *
69      * @param row row to render the blocking helper in
70      * @param menuRow menu used to generate the {@link NotificationInfo} view that houses the
71      *                blocking helper UI
72      * @return whether we're showing a blocking helper in the given notification row
73      */
perhapsShowBlockingHelper( ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow)74     boolean perhapsShowBlockingHelper(
75             ExpandableNotificationRow row, NotificationMenuRowPlugin menuRow) {
76         // We only show the blocking helper if:
77         // - User sentiment is negative (DEBUG flag can bypass)
78         // - The notification shade is fully expanded (guarantees we're not touching a HUN).
79         // - The row is blockable (i.e. not non-blockable)
80         // - The dismissed row is a valid group (>1 or 0 children) or the only child in the group
81         if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG)
82                 && mIsShadeExpanded
83                 && !row.getIsNonblockable()
84                 && (!row.isChildInGroup() || row.isOnlyChildInGroup())) {
85             // Dismiss any current blocking helper before continuing forward (only one can be shown
86             // at a given time).
87             dismissCurrentBlockingHelper();
88 
89             if (DEBUG) {
90                 Log.d(TAG, "Manager.perhapsShowBlockingHelper: Showing new blocking helper");
91             }
92             NotificationGutsManager manager = Dependency.get(NotificationGutsManager.class);
93 
94             // Enable blocking helper on the row before moving forward so everything in the guts is
95             // correctly prepped.
96             mBlockingHelperRow = row;
97             mBlockingHelperRow.setBlockingHelperShowing(true);
98 
99             // We don't care about the touch origin (x, y) since we're opening guts without any
100             // explicit user interaction.
101             manager.openGuts(mBlockingHelperRow, 0, 0, menuRow.getLongpressMenuItem(mContext));
102 
103             Dependency.get(MetricsLogger.class)
104                     .count(NotificationCounters.BLOCKING_HELPER_SHOWN, 1);
105             return true;
106         }
107         return false;
108     }
109 
110     /**
111      * Dismiss the currently showing blocking helper, if any, through a notification update.
112      *
113      * @return whether the blocking helper was dismissed
114      */
dismissCurrentBlockingHelper()115     boolean dismissCurrentBlockingHelper() {
116         if (!isBlockingHelperRowNull()) {
117             if (DEBUG) {
118                 Log.d(TAG, "Manager.dismissCurrentBlockingHelper: Dismissing current helper");
119             }
120             if (!mBlockingHelperRow.isBlockingHelperShowing()) {
121                 Log.e(TAG, "Manager.dismissCurrentBlockingHelper: "
122                         + "Non-null row is not showing a blocking helper");
123             }
124 
125             mBlockingHelperRow.setBlockingHelperShowing(false);
126             if (mBlockingHelperRow.isAttachedToWindow()) {
127                 Dependency.get(NotificationEntryManager.class).updateNotifications();
128             }
129             mBlockingHelperRow = null;
130             return true;
131         }
132         return false;
133     }
134 
135     /**
136      * Update the expansion status of the notification shade/stack.
137      *
138      * @param expandedHeight how much the shade is expanded ({code 0} indicating it's collapsed)
139      */
setNotificationShadeExpanded(float expandedHeight)140     public void setNotificationShadeExpanded(float expandedHeight) {
141         mIsShadeExpanded = expandedHeight > 0.0f;
142     }
143 
144     /**
145      * Returns whether the given package name is in the list of non-blockable packages.
146      */
isNonblockablePackage(String packageName)147     public boolean isNonblockablePackage(String packageName) {
148         return mNonBlockablePkgs.contains(packageName);
149     }
150 
151     @VisibleForTesting
isBlockingHelperRowNull()152     boolean isBlockingHelperRowNull() {
153         return mBlockingHelperRow == null;
154     }
155 
156     @VisibleForTesting
setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest)157     void setBlockingHelperRowForTest(ExpandableNotificationRow blockingHelperRowForTest) {
158         mBlockingHelperRow = blockingHelperRowForTest;
159     }
160 }
161