• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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