• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.app.notification.legacy.cts;
18 
19 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
20 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
21 import static android.service.notification.NotificationListenerService.INTERRUPTION_FILTER_PRIORITY;
22 
23 import static junit.framework.Assert.assertEquals;
24 import static junit.framework.Assert.assertNull;
25 import static junit.framework.TestCase.fail;
26 
27 import static java.lang.Thread.sleep;
28 
29 import android.Manifest;
30 import android.app.AutomaticZenRule;
31 import android.app.Instrumentation;
32 import android.app.NotificationManager;
33 import android.app.UiAutomation;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.IntentFilter;
37 import android.net.Uri;
38 import android.os.ParcelFileDescriptor;
39 import android.service.notification.Condition;
40 import android.service.notification.ZenModeConfig;
41 import android.util.ArraySet;
42 import android.util.Log;
43 
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.bedstead.harrier.DeviceState;
48 import com.android.bedstead.multiuser.annotations.RequireRunNotOnVisibleBackgroundNonProfileUser;
49 import com.android.compatibility.common.util.AmUtils;
50 import com.android.compatibility.common.util.SystemUtil;
51 
52 import junit.framework.Assert;
53 
54 import org.junit.After;
55 import org.junit.Before;
56 import org.junit.ClassRule;
57 import org.junit.Rule;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 
61 import java.io.FileInputStream;
62 import java.io.IOException;
63 import java.io.InputStream;
64 
65 @RunWith(AndroidJUnit4.class)
66 // TODO(b/355106764): Remove the annotation once zen/dnd supports visible background users.
67 @RequireRunNotOnVisibleBackgroundNonProfileUser(reason = "Zen/DND does not support visible"
68         + " background users. Only CPSes for the foreground user are bound.")
69 public class ConditionProviderServiceTest {
70     private static String TAG = "CpsTest";
71 
72     @ClassRule
73     @Rule
74     public static final DeviceState sDeviceState = new DeviceState();
75 
76     private NotificationManager mNm;
77     private Context mContext;
78     private ZenModeBroadcastReceiver mModeReceiver;
79     private IntentFilter mModeFilter;
80     private ArraySet<String> ids = new ArraySet<>();
81     private static final int BROADCAST_TIMEOUT_MS = 5000;
82 
83     @Before
setUp()84     public void setUp() throws Exception {
85         mContext = InstrumentationRegistry.getContext();
86         mModeReceiver = new ZenModeBroadcastReceiver();
87         mModeFilter = new IntentFilter();
88         mModeFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
89         mContext.registerReceiver(mModeReceiver, mModeFilter, Context.RECEIVER_EXPORTED);
90         toggleNotificationPolicyAccess(mContext.getPackageName(),
91                 InstrumentationRegistry.getInstrumentation(), true);
92         LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
93         SecondaryConditionProviderService.requestRebind(SecondaryConditionProviderService.getId());
94         mNm = (NotificationManager) mContext.getSystemService(
95                 Context.NOTIFICATION_SERVICE);
96 
97         SystemUtil.runWithShellPermissionIdentity(
98                 () -> mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL),
99                 Manifest.permission.STATUS_BAR_SERVICE);
100     }
101 
102     @After
tearDown()103     public void tearDown() {
104         try {
105             if (mNm != null) {
106                 try {
107                     for (String id : ids) {
108                         if (id != null) {
109                             if (!mNm.removeAutomaticZenRule(id)) {
110                                 throw new Exception("Could not remove rule " + id);
111                             }
112                             sleep(100);
113                             assertNull(mNm.getAutomaticZenRule(id));
114                         }
115                     }
116                 } finally {
117                     toggleNotificationPolicyAccess(mContext.getPackageName(),
118                             InstrumentationRegistry.getInstrumentation(), false);
119                     pollForConnection(LegacyConditionProviderService.class, false);
120                     pollForConnection(SecondaryConditionProviderService.class, false);
121                 }
122             }
123             if (mModeReceiver != null) {
124                 mContext.unregisterReceiver(mModeReceiver);
125             }
126         } catch (Exception e) {
127             Log.e(TAG, "Error cleaning up test", e);
128         }
129     }
130 
131     @Test
testUnboundCPSMaintainsCondition_addsNewRule()132     public void testUnboundCPSMaintainsCondition_addsNewRule() throws Exception {
133         // make sure service get bound
134         pollForConnection(SecondaryConditionProviderService.class, true);
135 
136         final ComponentName cn = SecondaryConditionProviderService.getId();
137 
138         // add rule
139         mModeReceiver.reset();
140 
141         addRule(cn, INTERRUPTION_FILTER_ALARMS, true);
142         pollForSubscribe(SecondaryConditionProviderService.getInstance());
143 
144         mModeReceiver.waitFor(1/*Secondary only*/, BROADCAST_TIMEOUT_MS);
145         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
146 
147         // unbind service
148         SecondaryConditionProviderService.getInstance().requestUnbind();
149 
150         // verify that DND state doesn't change
151         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
152 
153         // add a new rule
154         addRule(cn, INTERRUPTION_FILTER_NONE, true);
155 
156         // verify that the unbound service maintains it's DND vote
157         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
158     }
159 
160     @Test
testUnboundCPSMaintainsCondition_otherConditionChanges()161     public void testUnboundCPSMaintainsCondition_otherConditionChanges() throws Exception {
162         // make sure both services get bound
163         pollForConnection(LegacyConditionProviderService.class, true);
164         pollForConnection(SecondaryConditionProviderService.class, true);
165 
166         // add rules for both
167         mModeReceiver.reset();
168 
169         addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
170         pollForSubscribe(LegacyConditionProviderService.getInstance());
171 
172         addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
173         pollForSubscribe(SecondaryConditionProviderService.getInstance());
174 
175         mModeReceiver.waitFor(2/*Legacy and Secondary*/, BROADCAST_TIMEOUT_MS);
176         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
177 
178         // unbind one of the services
179         SecondaryConditionProviderService.getInstance().requestUnbind();
180 
181         // verify that DND state doesn't change
182         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
183 
184         // trigger a change in the bound service's condition
185         ((LegacyConditionProviderService) LegacyConditionProviderService.getInstance())
186                 .toggleDND(false);
187         sleep(500);
188 
189         // verify that the unbound service maintains it's DND vote
190         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
191     }
192 
193     @Test
testUnboundCPSMaintainsCondition_otherProviderRuleChanges()194     public void testUnboundCPSMaintainsCondition_otherProviderRuleChanges() throws Exception {
195         // make sure both services get bound
196         pollForConnection(LegacyConditionProviderService.class, true);
197         pollForConnection(SecondaryConditionProviderService.class, true);
198 
199         // add rules for both
200         mModeReceiver.reset();
201 
202         addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true);
203         pollForSubscribe(LegacyConditionProviderService.getInstance());
204 
205         addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true);
206         pollForSubscribe(SecondaryConditionProviderService.getInstance());
207 
208         mModeReceiver.waitFor(2/*Legacy and Secondary*/, BROADCAST_TIMEOUT_MS);
209         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
210 
211         // unbind one of the services
212         SecondaryConditionProviderService.getInstance().requestUnbind();
213 
214         // verify that DND state doesn't change
215         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
216 
217         // trigger a change in the bound service's rule
218         addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, false);
219 
220         // verify that the unbound service maintains it's DND vote
221         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
222     }
223 
224     @Test
testRequestRebindWhenLostAccess()225     public void testRequestRebindWhenLostAccess() throws Exception {
226         // make sure it gets bound
227         pollForConnection(LegacyConditionProviderService.class, true);
228 
229         // request unbind
230         LegacyConditionProviderService.getInstance().requestUnbind();
231 
232         // make sure it unbinds
233         pollForConnection(LegacyConditionProviderService.class, false);
234 
235         // lose dnd access
236         toggleNotificationPolicyAccess(mContext.getPackageName(),
237                 InstrumentationRegistry.getInstrumentation(), false);
238 
239         // try to rebind
240         LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
241 
242         // make sure it isn't rebound
243         try {
244             pollForConnection(LegacyConditionProviderService.class, true);
245             fail("Service got rebound after permission lost");
246         } catch (Exception e) {
247             // pass
248         }
249     }
250 
251     @Test
testRequestRebindWhenStillHasAccess()252     public void testRequestRebindWhenStillHasAccess() throws Exception {
253         // make sure it gets bound
254         pollForConnection(LegacyConditionProviderService.class, true);
255 
256         // request unbind
257         LegacyConditionProviderService.getInstance().requestUnbind();
258 
259         // make sure it unbinds
260         pollForConnection(LegacyConditionProviderService.class, false);
261 
262         // try to rebind
263         LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId());
264 
265         // make sure it did rebind
266         try {
267             pollForConnection(LegacyConditionProviderService.class, true);
268         } catch (Exception e) {
269             fail("Service should've been able to rebind");
270         }
271     }
272 
273     @Test
testMethodsExistAndDoNotThrow()274     public void testMethodsExistAndDoNotThrow() throws Exception {
275         // make sure it gets bound
276         pollForConnection(LegacyConditionProviderService.class, true);
277 
278         // request unbind
279         LegacyConditionProviderService.getInstance().onConnected();
280         LegacyConditionProviderService.getInstance().onRequestConditions(
281                 Condition.FLAG_RELEVANT_NOW);
282         LegacyConditionProviderService.getInstance().onSubscribe(Uri.EMPTY);
283         LegacyConditionProviderService.getInstance().onUnsubscribe(Uri.EMPTY);
284 
285     }
286 
addRule(ComponentName cn, int filter, boolean enabled)287     private void addRule(ComponentName cn, int filter, boolean enabled) {
288         final Uri conditionId = new Uri.Builder()
289                 .scheme(Condition.SCHEME)
290                 .authority(ZenModeConfig.SYSTEM_AUTHORITY)
291                 .appendPath(cn.toString())
292                 .build();
293         String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name",
294                 cn, conditionId, filter, enabled));
295         Log.d(TAG, "Created rule with id " + id);
296         ids.add(id);
297     }
298 
toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)299     private void toggleNotificationPolicyAccess(String packageName,
300             Instrumentation instrumentation, boolean on) throws IOException {
301 
302         String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName
303                 + " " + mContext.getUserId();
304 
305         runCommand(command, instrumentation);
306         AmUtils.waitForBroadcastBarrier();
307 
308         NotificationManager nm = mContext.getSystemService(NotificationManager.class);
309         Assert.assertEquals("Notification Policy Access Grant is " +
310                         nm.isNotificationPolicyAccessGranted() + " not " + on, on,
311                 nm.isNotificationPolicyAccessGranted());
312     }
313 
runCommand(String command, Instrumentation instrumentation)314     private void runCommand(String command, Instrumentation instrumentation) throws IOException {
315         UiAutomation uiAutomation = instrumentation.getUiAutomation();
316         // Execute command
317         try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
318             Assert.assertNotNull("Failed to execute shell command: " + command, fd);
319             // Wait for the command to finish by reading until EOF
320             try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
321                 byte[] buffer = new byte[4096];
322                 while (in.read(buffer) > 0) {}
323             } catch (IOException e) {
324                 throw new IOException("Could not read stdout of command: " + command, e);
325             }
326         } finally {
327             uiAutomation.destroy();
328         }
329     }
330 
pollForSubscribe(PollableConditionProviderService service)331     private void pollForSubscribe(PollableConditionProviderService service)  throws Exception {
332         int tries = 30;
333         int delayMs = 200;
334 
335         while (tries-- > 0 && !service.subscribed) {
336             try {
337                 sleep(delayMs);
338             } catch (InterruptedException e) {
339                 e.printStackTrace();
340             }
341         }
342 
343         if (!service.subscribed) {
344             Log.d(TAG, "not subscribed");
345             throw new Exception("Service never got onSubscribe()");
346         }
347     }
348 
pollForConnection(Class<? extends PollableConditionProviderService> service, boolean waitForConnection)349     private void pollForConnection(Class<? extends PollableConditionProviderService> service,
350             boolean waitForConnection) throws Exception {
351         int tries = 100;
352         int delayMs = 200;
353 
354         PollableConditionProviderService instance =
355                 (PollableConditionProviderService) service.getMethod("getInstance").invoke(null);
356 
357         while (tries-- > 0 && (waitForConnection ? instance == null : instance != null)) {
358             try {
359                 sleep(delayMs);
360             } catch (InterruptedException e) {
361                 e.printStackTrace();
362             }
363             instance = (PollableConditionProviderService) service.getMethod("getInstance")
364                     .invoke(null);
365         }
366 
367         if (waitForConnection && instance == null) {
368             Log.d(TAG, service.getName() + " not bound");
369             throw new Exception("CPS never bound");
370         } else if (!waitForConnection && instance != null) {
371             Log.d(TAG, service.getName() + " still bound");
372             throw new Exception("CPS still bound");
373         } else {
374             Log.d(TAG, service.getName() + " has a correct bind state");
375         }
376     }
377 }
378