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