• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.bedstead.nene.notifications;
18 
19 import android.service.notification.StatusBarNotification;
20 import android.util.Log;
21 
22 import com.android.bedstead.nene.exceptions.NeneException;
23 import com.android.queryable.Queryable;
24 import com.android.queryable.queries.NotificationQuery;
25 import com.android.queryable.queries.NotificationQueryHelper;
26 import com.android.queryable.queries.StringQuery;
27 import com.android.queryable.queries.StringQueryHelper;
28 
29 import java.time.Duration;
30 import java.time.Instant;
31 import java.util.Deque;
32 import java.util.HashSet;
33 import java.util.Set;
34 
35 /** Query for notifications. */
36 public final class NotificationListenerQuery implements Queryable {
37 
38     private static final String LOG_TAG = "NotificationListener";
39 
40     private final StringQueryHelper<NotificationListenerQuery> mPackageName =
41             new StringQueryHelper<>(this);
42     private final NotificationQueryHelper<NotificationListenerQuery> mNotification =
43             new NotificationQueryHelper<>(this);
44 
45     private boolean mHasStartedFetchingResults = false;
46     private final Deque<StatusBarNotification> mStatusBarNotifications;
47     private int mSkippedPollResults = 0;
48     private Set<StatusBarNotification> mNonMatchingNotifications = new HashSet<>();
49 
NotificationListenerQuery(Deque<StatusBarNotification> statusBarNotifications)50     NotificationListenerQuery(Deque<StatusBarNotification> statusBarNotifications) {
51         mStatusBarNotifications = statusBarNotifications;
52     }
53 
54     /** Query by package name. */
wherePackageName()55     public StringQuery<NotificationListenerQuery> wherePackageName() {
56         if (mHasStartedFetchingResults) {
57             throw new IllegalStateException("Cannot modify query after fetching results");
58         }
59         return mPackageName;
60     }
61 
62     /** Query by notification content. */
whereNotification()63     public NotificationQuery<NotificationListenerQuery> whereNotification() {
64         if (mHasStartedFetchingResults) {
65             throw new IllegalStateException("Cannot modify query after fetching results");
66         }
67         return mNotification;
68     }
69 
70     /** Poll for matching notifications. */
poll()71     public StatusBarNotification poll() {
72         return poll(Duration.ofSeconds(30));
73     }
74 
75     /** Poll for matching notifications. */
poll(Duration timeout)76     public StatusBarNotification poll(Duration timeout) {
77         mHasStartedFetchingResults = true;
78         Instant endTime = Instant.now().plus(timeout);
79 
80         while (Instant.now().isBefore(endTime)) {
81             StatusBarNotification nextResult = get(mSkippedPollResults);
82             if (nextResult != null) {
83                 mSkippedPollResults++;
84                 return nextResult;
85             }
86 
87             try {
88                 Thread.sleep(500);
89             } catch (InterruptedException e) {
90                 throw new NeneException("Interrupted while polling", e);
91             }
92         }
93 
94         return null;
95     }
96 
get(int skipResults)97     private StatusBarNotification get(int skipResults) {
98         mHasStartedFetchingResults = true;
99         for (StatusBarNotification m : mStatusBarNotifications) {
100             if (matches(m)) {
101                 skipResults -= 1;
102                 if (skipResults < 0) {
103                     return m;
104                 }
105             } else {
106                 Log.d(LOG_TAG, "Found non-matching notification " + m);
107                 mNonMatchingNotifications.add(m);
108             }
109         }
110 
111         return null;
112     }
113 
114     /**
115      * Get notifications which were received but didn't match the query.
116      */
nonMatchingNotifications()117     Set<StatusBarNotification> nonMatchingNotifications() {
118         return mNonMatchingNotifications;
119     }
120 
matches(StatusBarNotification sbn)121     private boolean matches(StatusBarNotification sbn) {
122         return StringQueryHelper.matches(mPackageName, sbn.getPackageName())
123                 && NotificationQueryHelper.matches(mNotification, sbn.getNotification());
124     }
125 
126     @Override
isEmptyQuery()127     public boolean isEmptyQuery() {
128         return Queryable.isEmptyQuery(mPackageName)
129                 && Queryable.isEmptyQuery(mNotification);
130     }
131 
132     @Override
describeQuery(String fieldName)133     public String describeQuery(String fieldName) {
134         return "{" + Queryable.joinQueryStrings(
135                 mPackageName.describeQuery("packageName"),
136                 mNotification.describeQuery("notification")
137         ) + "}";
138     }
139 
140     @Override
toString()141     public String toString() {
142         return describeQuery("");
143     }
144 }
145