1 /*
2  * Copyright (C) 2023 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.jobscheduler.cts;
18 
19 import static com.android.compatibility.common.util.TestUtils.waitUntil;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNotNull;
23 
24 import android.app.NotificationManager;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.service.notification.NotificationListenerService;
28 import android.service.notification.StatusBarNotification;
29 import android.util.Log;
30 
31 import com.android.compatibility.common.util.ScreenUtils;
32 import com.android.compatibility.common.util.SystemUtil;
33 
34 import java.util.ArrayList;
35 import java.util.concurrent.ArrayBlockingQueue;
36 import java.util.concurrent.BlockingQueue;
37 import java.util.concurrent.TimeUnit;
38 
39 public class TestNotificationListener extends NotificationListenerService {
40     private static final String TAG = "TestNotificationListener";
41     private static final long SLEEP_TIME_MS = 1000;
42 
43     private final ArrayList<String> mTestPackages = new ArrayList<>();
44 
45     public BlockingQueue<StatusBarNotification> mPosted = new ArrayBlockingQueue<>(10);
46     public ArrayList<StatusBarNotification> mRemoved = new ArrayList<>();
47 
48     private static TestNotificationListener sNotificationListenerInstance = null;
49     boolean mIsConnected;
50 
getId()51     public static String getId() {
52         return String.format("%s/%s", TestNotificationListener.class.getPackage().getName(),
53                 TestNotificationListener.class.getName());
54     }
55 
getComponentName()56     public static ComponentName getComponentName() {
57         return new ComponentName(TestNotificationListener.class.getPackage().getName(),
58                 TestNotificationListener.class.getName());
59     }
60 
61     @Override
onCreate()62     public void onCreate() {
63         super.onCreate();
64     }
65 
66     @Override
onListenerConnected()67     public void onListenerConnected() {
68         super.onListenerConnected();
69         Log.d(TAG, "onListenerConnected #" + this.hashCode());
70         sNotificationListenerInstance = this;
71         mIsConnected = true;
72     }
73 
74     @Override
onListenerDisconnected()75     public void onListenerDisconnected() {
76         Log.d(TAG, "onListenerDisconnected #" + this.hashCode());
77         sNotificationListenerInstance = null;
78         mIsConnected = false;
79     }
80 
getInstance()81     public static TestNotificationListener getInstance() throws Exception {
82         waitUntil("Notification listener not instantiated", 15 /* seconds */,
83                 () -> sNotificationListenerInstance != null);
84         return sNotificationListenerInstance;
85     }
86 
addTestPackage(String packageName)87     public void addTestPackage(String packageName) {
88         mTestPackages.add(packageName);
89     }
90 
getFirstNotificationFromPackage(String packageName)91     public StatusBarNotification getFirstNotificationFromPackage(String packageName)
92             throws Exception {
93         final long maxWaitMs = 30_000L;
94         final int numAttempts = (int) (maxWaitMs / SLEEP_TIME_MS) + 1;
95         for (int i = 0; i < numAttempts; ++i) {
96             StatusBarNotification sbn = mPosted.poll(SLEEP_TIME_MS, TimeUnit.MILLISECONDS);
97             if (sbn != null && sbn.getPackageName().equals(packageName)) {
98                 return sbn;
99             }
100         }
101         return null;
102     }
103 
toggleListenerAccess(Context context, boolean on)104     public static void toggleListenerAccess(Context context, boolean on) throws Exception {
105         SystemUtil.runShellCommand("cmd notification"
106                 + " " + (on ? "allow_listener" : "disallow_listener")
107                 + " " + getId());
108 
109         final ComponentName listenerComponent = getComponentName();
110         final NotificationManager nm = context.getSystemService(NotificationManager.class);
111         assertEquals(listenerComponent + " has not been " + (on ? "allowed" : "disallowed"),
112                 on, nm.isNotificationListenerAccessGranted(listenerComponent));
113     }
114 
resetData()115     public void resetData() {
116         mPosted.clear();
117         mRemoved.clear();
118     }
119 
120     @Override
onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap)121     public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
122         Log.v(TAG, "notification posted: " + sbn);
123         if (!mTestPackages.contains(sbn.getPackageName())) {
124             Log.d(TAG, "Ignoring notification from " + sbn.getPackageName()
125                     + " because it's not in " + mTestPackages);
126             return;
127         }
128         Log.v(TAG, "adding to added: " + sbn);
129         mPosted.add(sbn);
130     }
131 
132     @Override
onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap)133     public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
134         Log.v(TAG, "notification removed: " + sbn);
135         if (!mTestPackages.contains(sbn.getPackageName())) {
136             return;
137         }
138         Log.v(TAG, "adding to removed: " + sbn);
139         mRemoved.add(sbn);
140     }
141 
142     @Override
onNotificationRankingUpdate(RankingMap rankingMap)143     public void onNotificationRankingUpdate(RankingMap rankingMap) {
144     }
145 
146     public static class NotificationHelper implements AutoCloseable {
147         private final Context mContext;
148         private final TestNotificationListener mNotificationListener;
149         private final String mTestPackage;
150 
NotificationHelper(Context context, String testPackage)151         public NotificationHelper(Context context, String testPackage) throws Exception {
152             mContext = context;
153             TestNotificationListener.toggleListenerAccess(context, true);
154             mNotificationListener = TestNotificationListener.getInstance();
155             mNotificationListener.addTestPackage(testPackage);
156             mTestPackage = testPackage;
157             ScreenUtils.setScreenOn(true);
158         }
159 
assertNotificationsRemoved()160         public void assertNotificationsRemoved() throws Exception {
161             waitUntil("Notification wasn't removed", 15 /* seconds */,
162                     () -> {
163                         StatusBarNotification[] activeNotifications =
164                                 mNotificationListener.getActiveNotifications();
165                         for (StatusBarNotification sbn : activeNotifications) {
166                             if (sbn.getPackageName().equals(mTestPackage)) {
167                                 return false;
168                             }
169                         }
170                         return true;
171                     });
172         }
173 
clickNotification()174         public void clickNotification() throws Exception {
175             StatusBarNotification sbn =
176                     mNotificationListener.getFirstNotificationFromPackage(mTestPackage);
177             assertNotNull(sbn);
178             sbn.getNotification().contentIntent.send();
179         }
180 
close()181         public void close() throws Exception {
182             TestNotificationListener.toggleListenerAccess(mContext, false);
183         }
184 
getNotification()185         public StatusBarNotification getNotification() throws Exception {
186             return mNotificationListener.getFirstNotificationFromPackage(mTestPackage);
187         }
188     }
189 }
190