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