1 package org.robolectric.shadows; 2 3 import static java.util.stream.Collectors.toCollection; 4 5 import android.app.Notification; 6 import android.content.ComponentName; 7 import android.os.Build.VERSION; 8 import android.os.Build.VERSION_CODES; 9 import android.os.UserHandle; 10 import android.service.notification.NotificationListenerService; 11 import android.service.notification.NotificationListenerService.Ranking; 12 import android.service.notification.NotificationListenerService.RankingMap; 13 import android.service.notification.StatusBarNotification; 14 import com.google.common.collect.ImmutableSet; 15 import java.util.ArrayList; 16 import java.util.Collections; 17 import java.util.Iterator; 18 import java.util.List; 19 import java.util.concurrent.atomic.AtomicInteger; 20 import org.robolectric.annotation.Implementation; 21 import org.robolectric.annotation.Implements; 22 import org.robolectric.annotation.Resetter; 23 import org.robolectric.util.ReflectionHelpers; 24 import org.robolectric.util.ReflectionHelpers.ClassParameter; 25 26 /** Shadow implementation of {@link NotificationListenerService}. */ 27 @Implements(value = NotificationListenerService.class) 28 public class ShadowNotificationListenerService extends ShadowService { 29 private static final AtomicInteger rebindRequestCount = new AtomicInteger(0); 30 31 private final List<StatusBarNotification> activeNotifications = 32 Collections.synchronizedList(new ArrayList<>()); 33 private final AtomicInteger interruptionFilter = 34 new AtomicInteger(NotificationListenerService.INTERRUPTION_FILTER_UNKNOWN); 35 private final AtomicInteger hint = new AtomicInteger(0); 36 private final AtomicInteger unbindRequestCount = new AtomicInteger(0); 37 private final RankingMap emptyRankingMap = createEmptyRankingMap(); 38 39 /** 40 * Adds the given {@link Notification} to the list of active Notifications. A corresponding {@link 41 * StatusBarNotification} will be generated from this Notification, which will be included in the 42 * result of {@link NotificationListenerService#getActiveNotifications}. 43 * 44 * @return the key of the generated {@link StatusBarNotification} 45 */ addActiveNotification(String packageName, int id, Notification notification)46 public String addActiveNotification(String packageName, int id, Notification notification) { 47 StatusBarNotification statusBarNotification = 48 new StatusBarNotification( 49 /* pkg= */ packageName, 50 /* opPkg= */ packageName, 51 id, 52 /* tag= */ null, 53 /* uid= */ 0, 54 /* initialPid= */ 0, 55 /* score= */ 0, 56 notification, 57 UserHandle.CURRENT, 58 notification.when); 59 return addActiveNotification(statusBarNotification); 60 } 61 62 /** 63 * Adds the given {@link StatusBarNotification} to the list of active Notifications. The given 64 * {@link StatusBarNotification} will be included in the result of {@link 65 * NotificationListenerService#getActiveNotifications}. 66 * 67 * @return the key of the given {@link StatusBarNotification} 68 */ addActiveNotification(StatusBarNotification statusBarNotification)69 public String addActiveNotification(StatusBarNotification statusBarNotification) { 70 activeNotifications.add(statusBarNotification); 71 return statusBarNotification.getKey(); 72 } 73 74 @Implementation(minSdk = VERSION_CODES.N) requestRebind(ComponentName componentName)75 protected static void requestRebind(ComponentName componentName) { 76 rebindRequestCount.incrementAndGet(); 77 } 78 79 @Implementation(minSdk = VERSION_CODES.N) requestUnbind()80 protected void requestUnbind() { 81 unbindRequestCount.incrementAndGet(); 82 } 83 84 @Implementation cancelAllNotifications()85 protected void cancelAllNotifications() { 86 activeNotifications.clear(); 87 } 88 89 @Implementation cancelNotification(String key)90 protected void cancelNotification(String key) { 91 synchronized (activeNotifications) { 92 Iterator<StatusBarNotification> iterator = activeNotifications.iterator(); 93 while (iterator.hasNext()) { 94 StatusBarNotification statusBarNotification = iterator.next(); 95 if (statusBarNotification.getKey().equals(key)) { 96 iterator.remove(); 97 break; 98 } 99 } 100 } 101 } 102 103 /** 104 * Returns zero or more notifications, added by {@link #addActiveNotification}, that match one of 105 * the provided keys. 106 * 107 * @param keys the keys to match 108 * @param trim ignored, trimming is not supported 109 */ 110 @Implementation getActiveNotifications(String[] keys, int trim)111 protected StatusBarNotification[] getActiveNotifications(String[] keys, int trim) { 112 if (keys == null) { 113 return activeNotifications.toArray(new StatusBarNotification[0]); 114 } 115 116 ImmutableSet<String> keySet = ImmutableSet.copyOf(keys); 117 return activeNotifications.stream() 118 .filter(notification -> keySet.contains(notification.getKey())) 119 .collect(toCollection(ArrayList::new)) 120 .toArray(new StatusBarNotification[0]); 121 } 122 123 @Implementation requestInterruptionFilter(int interruptionFilter)124 protected void requestInterruptionFilter(int interruptionFilter) { 125 this.interruptionFilter.set(interruptionFilter); 126 } 127 128 @Implementation getCurrentInterruptionFilter()129 protected int getCurrentInterruptionFilter() { 130 return interruptionFilter.get(); 131 } 132 133 @Implementation requestListenerHints(int hint)134 protected void requestListenerHints(int hint) { 135 this.hint.set(hint); 136 } 137 138 @Implementation getCurrentListenerHints()139 protected int getCurrentListenerHints() { 140 return hint.get(); 141 } 142 143 @Implementation getCurrentRanking()144 protected RankingMap getCurrentRanking() { 145 return emptyRankingMap; 146 } 147 148 /** Returns the number of times rebind was requested. */ getRebindRequestCount()149 public static int getRebindRequestCount() { 150 return rebindRequestCount.get(); 151 } 152 153 /** Returns the number of times unbind was requested. */ getUnbindRequestCount()154 public int getUnbindRequestCount() { 155 return unbindRequestCount.get(); 156 } 157 158 /** Resets this shadow instance. */ 159 @Resetter reset()160 public static void reset() { 161 rebindRequestCount.set(0); 162 } 163 createEmptyRankingMap()164 private static RankingMap createEmptyRankingMap() { 165 return VERSION.SDK_INT < VERSION_CODES.Q 166 ? ReflectionHelpers.callConstructor(RankingMap.class) 167 : ReflectionHelpers.callConstructor( 168 RankingMap.class, ClassParameter.from(Ranking[].class, new Ranking[] {})); 169 } 170 } 171