1 /*
2  * Copyright (C) 2020 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;
18 
19 import static android.service.notification.NotificationListenerService.Ranking;
20 
21 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.ENABLE_NAS_FEEDBACK;
22 
23 import android.app.NotificationManager;
24 import android.content.Context;
25 import android.os.Handler;
26 import android.provider.DeviceConfig;
27 import android.util.SparseArray;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.internal.R;
32 import com.android.systemui.dagger.SysUISingleton;
33 import com.android.systemui.dagger.qualifiers.Main;
34 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
35 import com.android.systemui.util.DeviceConfigProxy;
36 
37 import javax.inject.Inject;
38 
39 /**
40  * Determines whether to show any indicators or controls related to notification assistant.
41  *
42  * Flags protect any changes from being shown. Notifications that are adjusted by the assistant
43  * should show an indicator.
44  */
45 @SysUISingleton
46 public class AssistantFeedbackController {
47     private final Context mContext;
48     private final Handler mHandler;
49     private final DeviceConfigProxy mDeviceConfigProxy;
50 
51     public static final int STATUS_UNCHANGED = 0;
52     public static final int STATUS_ALERTED = 1;
53     public static final int STATUS_SILENCED = 2;
54     public static final int STATUS_PROMOTED = 3;
55     public static final int STATUS_DEMOTED = 4;
56 
57     private final SparseArray<FeedbackIcon> mIcons;
58 
59     private volatile boolean mFeedbackEnabled;
60 
61     private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
62             new DeviceConfig.OnPropertiesChangedListener() {
63                 @Override
64                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
65                     if (properties.getKeyset().contains(ENABLE_NAS_FEEDBACK)) {
66                         mFeedbackEnabled = properties.getBoolean(
67                                 ENABLE_NAS_FEEDBACK, false);
68                     }
69                 }
70             };
71 
72     /** Injected constructor */
73     @Inject
AssistantFeedbackController(@ain Handler handler, Context context, DeviceConfigProxy proxy)74     public AssistantFeedbackController(@Main Handler handler,
75             Context context, DeviceConfigProxy proxy) {
76         mHandler = handler;
77         mContext = context;
78         mDeviceConfigProxy = proxy;
79         mFeedbackEnabled = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
80                 ENABLE_NAS_FEEDBACK, false);
81         mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
82                 this::postToHandler, mPropertiesChangedListener);
83         // Populate the array of statuses.
84         mIcons = new SparseArray<>(4);
85         mIcons.set(STATUS_ALERTED, new FeedbackIcon(R.drawable.ic_feedback_alerted,
86                 R.string.notification_feedback_indicator_alerted));
87         mIcons.set(STATUS_SILENCED, new FeedbackIcon(R.drawable.ic_feedback_silenced,
88                 R.string.notification_feedback_indicator_silenced));
89         mIcons.set(STATUS_PROMOTED, new FeedbackIcon(R.drawable.ic_feedback_uprank,
90                 R.string.notification_feedback_indicator_promoted));
91         mIcons.set(STATUS_DEMOTED, new FeedbackIcon(R.drawable.ic_feedback_downrank,
92                 R.string.notification_feedback_indicator_demoted));
93     }
94 
postToHandler(Runnable r)95     private void postToHandler(Runnable r) {
96         this.mHandler.post(r);
97     }
98 
99     /**
100      * Determines whether to show any user controls related to the assistant based on the
101      * DeviceConfig flag value
102      */
isFeedbackEnabled()103     public boolean isFeedbackEnabled() {
104         return mFeedbackEnabled;
105     }
106 
107     /**
108      * Get the feedback status according to assistant's adjustments
109      *
110      * @param entry Notification Entry to show feedback for
111      */
getFeedbackStatus(NotificationEntry entry)112     public int getFeedbackStatus(NotificationEntry entry) {
113         if (!isFeedbackEnabled()) {
114             return STATUS_UNCHANGED;
115         }
116         Ranking ranking = entry.getRanking();
117         int oldImportance = ranking.getChannel().getImportance();
118         int newImportance = ranking.getImportance();
119         if (oldImportance < NotificationManager.IMPORTANCE_DEFAULT
120                 && newImportance >= NotificationManager.IMPORTANCE_DEFAULT) {
121             return STATUS_ALERTED;
122         } else if (oldImportance >= NotificationManager.IMPORTANCE_DEFAULT
123                 && newImportance < NotificationManager.IMPORTANCE_DEFAULT) {
124             return STATUS_SILENCED;
125         } else if (oldImportance < newImportance
126                 || ranking.getRankingAdjustment() == Ranking.RANKING_PROMOTED) {
127             return STATUS_PROMOTED;
128         } else if (oldImportance > newImportance
129                 || ranking.getRankingAdjustment() == Ranking.RANKING_DEMOTED) {
130             return STATUS_DEMOTED;
131         } else {
132             return STATUS_UNCHANGED;
133         }
134     }
135 
136     /**
137      * Get the feedback indicator image and content description resources according to assistant's
138      * changes on this notification's rank or importance.
139      *
140      * @param entry Notification Entry to show feedback for
141      */
142     @Nullable
getFeedbackIcon(NotificationEntry entry)143     public FeedbackIcon getFeedbackIcon(NotificationEntry entry) {
144         int feedbackStatus = getFeedbackStatus(entry);
145         return mIcons.get(feedbackStatus);
146     }
147 
148     /**
149      * Get the inline settings description resource according to assistant's changes on this
150      * notification's rank or importance.
151      *
152      * @param entry Notification Entry to show feedback for
153      */
getInlineDescriptionResource(NotificationEntry entry)154     public int getInlineDescriptionResource(NotificationEntry entry) {
155         int feedbackStatus = getFeedbackStatus(entry);
156         switch (feedbackStatus) {
157             case STATUS_ALERTED:
158                 return com.android.systemui.res.R.string.notification_channel_summary_automatic_alerted;
159             case STATUS_SILENCED:
160                 return com.android.systemui.res.R.string
161                         .notification_channel_summary_automatic_silenced;
162             case STATUS_PROMOTED:
163                 return com.android.systemui.res.R.string
164                         .notification_channel_summary_automatic_promoted;
165             case STATUS_DEMOTED:
166                 return com.android.systemui.res.R.string.notification_channel_summary_automatic_demoted;
167             default:
168                 return com.android.systemui.res.R.string.notification_channel_summary_automatic;
169         }
170     }
171 }
172