xref: /aosp_15_r20/cts/tests/app/shared/src/android/app/stubs/shared/TestNotificationAssistant.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2022 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 android.app.stubs.shared;
18 
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.NotificationManager;
22 import android.os.Bundle;
23 import android.os.ConditionVariable;
24 import android.service.notification.Adjustment;
25 import android.service.notification.NotificationAssistantService;
26 import android.service.notification.StatusBarNotification;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.CountDownLatch;
34 
35 public class TestNotificationAssistant extends NotificationAssistantService {
36     public static final String TAG = "TestNotificationAssistant";
37     public static final String PKG = "android.app.stubs";
38     private static final long CONNECTION_TIMEOUT_MS = 5000;
39 
40     private static TestNotificationAssistant sNotificationAssistantInstance = null;
41     boolean mIsConnected;
42     public List<String> mCurrentCapabilities = new ArrayList<>();
43     public boolean mIsPanelOpen = false;
44     public String mSnoozedKey;
45     public String mSnoozedUntilContext;
46     public boolean mNotificationVisible = false;
47     public int mNotificationId = 1357;
48     public int mNotificationSeenCount = 0;
49     public int mNotificationClickCount = 0;
50     public int mNotificationRank = -1;
51     public int mNotificationFeedback = 0;
52 
53     public boolean mMarkSensitiveContent = false;
54     public ArrayList<Notification.Action> mSmartActions = null;
55     public ArrayList<CharSequence> mSmartReplies = null;
56     private NotificationManager mNotificationManager;
57 
58     public Map<String, Integer> mRemoved = new HashMap<>();
59     private CountDownLatch mRankingUpdateLatch = null;
60     private CountDownLatch mAllowedAdjustmentsLatch = null;
61 
62     /**
63      * This controls whether there is a listener connected or not. Depending on the method, if the
64      * caller tries to use a listener after it has disconnected, NMS can throw a SecurityException.
65      *
66      * There is no race between onListenerConnected() and onListenerDisconnected() because they are
67      * called in the same thread. The value that getInstance() sees is guaranteed to be the value
68      * that was set by onListenerConnected() because of the happens-before established by the
69      * condition variable.
70      */
71     private static final ConditionVariable INSTANCE_AVAILABLE = new ConditionVariable(false);
72 
73     @Override
onCreate()74     public void onCreate() {
75         super.onCreate();
76         mNotificationManager = getSystemService(NotificationManager.class);
77     }
78 
resetData()79     public void resetData() {
80         mIsPanelOpen = false;
81         mCurrentCapabilities.clear();
82         mNotificationVisible = false;
83         mNotificationSeenCount = 0;
84         mNotificationClickCount = 0;
85         mNotificationRank = -1;
86         mNotificationFeedback = 0;
87         mSnoozedKey = null;
88         mSnoozedUntilContext = null;
89         mRemoved.clear();
90     }
91 
92     @Override
onListenerConnected()93     public void onListenerConnected() {
94         super.onListenerConnected();
95         sNotificationAssistantInstance = this;
96         mCurrentCapabilities = mNotificationManager.getAllowedAssistantAdjustments();
97         INSTANCE_AVAILABLE.open();
98         Log.d(TAG, "TestNotificationAssistant connected");
99         mIsConnected = true;
100     }
101 
102     @Override
onListenerDisconnected()103     public void onListenerDisconnected() {
104         INSTANCE_AVAILABLE.close();
105         Log.d(TAG, "TestNotificationAssistant disconnected");
106         sNotificationAssistantInstance = null;
107         mIsConnected = false;
108     }
109 
getInstance()110     public static TestNotificationAssistant getInstance() {
111         if (INSTANCE_AVAILABLE.block(CONNECTION_TIMEOUT_MS)) {
112             return sNotificationAssistantInstance;
113         }
114         return null;
115     }
116 
117     @Override
onNotificationSnoozedUntilContext(StatusBarNotification statusBarNotification, String s)118     public void onNotificationSnoozedUntilContext(StatusBarNotification statusBarNotification,
119             String s) {
120         mSnoozedKey = statusBarNotification.getKey();
121         mSnoozedUntilContext = s;
122     }
123 
124     @Override
onNotificationEnqueued(StatusBarNotification sbn)125     public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
126         return null;
127     }
128 
129     @Override
onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel, RankingMap rankingMap)130     public Adjustment onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel,
131             RankingMap rankingMap) {
132         Bundle signals = new Bundle();
133         Ranking ranking = new Ranking();
134         rankingMap.getRanking(sbn.getKey(), ranking);
135         mNotificationRank = ranking.getRank();
136         signals.putInt(Adjustment.KEY_USER_SENTIMENT, Ranking.USER_SENTIMENT_POSITIVE);
137         if (mMarkSensitiveContent) {
138             signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
139         }
140         if (mSmartActions != null) {
141             signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, mSmartActions);
142         }
143         if (mSmartReplies != null) {
144             signals.putCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES, mSmartReplies);
145         }
146         return new Adjustment(sbn.getPackageName(), sbn.getKey(), signals, "",
147                 sbn.getUser());
148     }
149 
150     @Override
onAllowedAdjustmentsChanged()151     public void onAllowedAdjustmentsChanged() {
152         mCurrentCapabilities = mNotificationManager.getAllowedAssistantAdjustments();
153         maybeUpdateLatch(mAllowedAdjustmentsLatch);
154     }
155 
resetNotificationVisibilityCounts()156     public void resetNotificationVisibilityCounts() {
157         mNotificationSeenCount = 0;
158     }
159 
160     @Override
onNotificationVisibilityChanged(String key, boolean isVisible)161     public void onNotificationVisibilityChanged(String key, boolean isVisible) {
162         if (key.contains(getPackageName() + "|" + mNotificationId)) {
163             mNotificationVisible = isVisible;
164         }
165     }
166 
167     @Override
onNotificationsSeen(List<String> keys)168     public void onNotificationsSeen(List<String> keys) {
169         mNotificationSeenCount += keys.size();
170     }
171 
172     @Override
onPanelHidden()173     public void onPanelHidden() {
174         mIsPanelOpen = false;
175     }
176 
177     @Override
onPanelRevealed(int items)178     public void onPanelRevealed(int items) {
179         mIsPanelOpen = true;
180     }
181 
resetNotificationClickCount()182     public void resetNotificationClickCount() {
183         mNotificationClickCount = 0;
184     }
185 
186     @Override
onNotificationClicked(String key)187     public void onNotificationClicked(String key) {
188         mNotificationClickCount++;
189     }
190 
191     @Override
onNotificationFeedbackReceived(String key, RankingMap rankingMap, Bundle feedback)192     public void onNotificationFeedbackReceived(String key, RankingMap rankingMap, Bundle feedback) {
193         mNotificationFeedback = feedback.getInt(FEEDBACK_RATING, 0);
194     }
195 
196     @Override
onNotificationPosted(StatusBarNotification sbn)197     public void onNotificationPosted(StatusBarNotification sbn) {
198 
199     }
200 
201     @Override
onNotificationRemoved(StatusBarNotification sbn)202     public void onNotificationRemoved(StatusBarNotification sbn) {
203         if (sbn == null) {
204             return;
205         }
206         mRemoved.put(sbn.getKey(), -1);
207     }
208 
209     @Override
onNotificationRankingUpdate(RankingMap rankingMap)210     public void onNotificationRankingUpdate(RankingMap rankingMap) {
211         maybeUpdateLatch(mRankingUpdateLatch);
212     }
213 
214     @Override
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason)215     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
216             int reason) {
217         if (sbn == null) {
218             return;
219         }
220         mRemoved.put(sbn.getKey(), reason);
221     }
222 
setAllowedAdjustmentCountdown(int countDownNumber)223     public CountDownLatch setAllowedAdjustmentCountdown(int countDownNumber) {
224         mAllowedAdjustmentsLatch = new CountDownLatch(countDownNumber);
225         return mAllowedAdjustmentsLatch;
226     }
227 
setRankingUpdateCountDown(int countDownNumber)228     public CountDownLatch setRankingUpdateCountDown(int countDownNumber) {
229         mRankingUpdateLatch = new CountDownLatch(countDownNumber);
230         return mRankingUpdateLatch;
231     }
232 
maybeUpdateLatch(CountDownLatch latch)233     private void maybeUpdateLatch(CountDownLatch latch) {
234         if (latch != null) {
235             latch.countDown();
236         }
237     }
238 }
239