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