1/*
2 * Copyright (C) 2016 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 */
16package com.android.server.notification;
17
18import static android.app.Notification.GROUP_ALERT_ALL;
19import static android.app.Notification.GROUP_ALERT_CHILDREN;
20import static android.app.Notification.GROUP_ALERT_SUMMARY;
21import static android.app.NotificationManager.IMPORTANCE_HIGH;
22
23import static junit.framework.Assert.assertNull;
24import static junit.framework.Assert.assertTrue;
25
26import static org.mockito.Matchers.anyBoolean;
27import static org.mockito.Matchers.anyInt;
28import static org.mockito.Matchers.anyObject;
29import static org.mockito.Matchers.anyString;
30import static org.mockito.Matchers.argThat;
31import static org.mockito.Matchers.eq;
32import static org.mockito.Mockito.never;
33import static org.mockito.Mockito.spy;
34import static org.mockito.Mockito.timeout;
35import static org.mockito.Mockito.times;
36import static org.mockito.Mockito.verify;
37import static org.mockito.Mockito.when;
38
39import android.app.ActivityManager;
40import android.app.Notification;
41import android.app.Notification.Builder;
42import android.app.NotificationChannel;
43import android.app.NotificationManager;
44import android.content.Context;
45import android.content.pm.PackageManager;
46import android.graphics.Color;
47import android.media.AudioAttributes;
48import android.media.AudioManager;
49import android.net.Uri;
50import android.os.Handler;
51import android.os.RemoteException;
52import android.os.UserHandle;
53import android.os.VibrationEffect;
54import android.os.Vibrator;
55import android.provider.Settings;
56import android.service.notification.StatusBarNotification;
57import android.support.test.runner.AndroidJUnit4;
58import android.test.suitebuilder.annotation.SmallTest;
59
60import com.android.server.lights.Light;
61
62import org.junit.Before;
63import org.junit.Test;
64import org.junit.runner.RunWith;
65import org.mockito.ArgumentMatcher;
66import org.mockito.Mock;
67import org.mockito.Mockito;
68import org.mockito.MockitoAnnotations;
69
70@SmallTest
71@RunWith(AndroidJUnit4.class)
72public class BuzzBeepBlinkTest extends NotificationTestCase {
73
74    @Mock AudioManager mAudioManager;
75    @Mock Vibrator mVibrator;
76    @Mock android.media.IRingtonePlayer mRingtonePlayer;
77    @Mock Light mLight;
78    @Mock Handler mHandler;
79
80    private NotificationManagerService mService;
81    private String mPkg = "com.android.server.notification";
82    private int mId = 1001;
83    private int mOtherId = 1002;
84    private String mTag = null;
85    private int mUid = 1000;
86    private int mPid = 2000;
87    private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
88
89    private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
90    private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
91
92    private static final long[] CUSTOM_VIBRATION = new long[] {
93            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
94            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
95            300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
96    private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
97    private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder()
98            .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
99            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
100            .build();
101    private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
102    private static final int CUSTOM_LIGHT_ON = 10000;
103    private static final int CUSTOM_LIGHT_OFF = 10000;
104    private static final long[] FALLBACK_VIBRATION_PATTERN = new long[] {100, 100, 100};
105    private static final VibrationEffect FALLBACK_VIBRATION =
106            VibrationEffect.createWaveform(FALLBACK_VIBRATION_PATTERN, -1);
107    private static final int MAX_VIBRATION_DELAY = 1000;
108
109    @Before
110    public void setUp() {
111        MockitoAnnotations.initMocks(this);
112
113        when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
114        when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
115        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
116        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
117
118        mService = new NotificationManagerService(getContext());
119        mService.setAudioManager(mAudioManager);
120        mService.setVibrator(mVibrator);
121        mService.setSystemReady(true);
122        mService.setHandler(mHandler);
123        mService.setLights(mLight);
124        mService.setScreenOn(false);
125        mService.setFallbackVibrationPattern(FALLBACK_VIBRATION_PATTERN);
126    }
127
128    //
129    // Convenience functions for creating notification records
130    //
131
132    private NotificationRecord getNoisyOtherNotification() {
133        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
134                true /* noisy */, true /* buzzy*/, false /* lights */);
135    }
136
137    private NotificationRecord getBeepyNotification() {
138        return getNotificationRecord(mId, false /* insistent */, false /* once */,
139                true /* noisy */, false /* buzzy*/, false /* lights */);
140    }
141
142    private NotificationRecord getBeepyOnceNotification() {
143        return getNotificationRecord(mId, false /* insistent */, true /* once */,
144                true /* noisy */, false /* buzzy*/, false /* lights */);
145    }
146
147    private NotificationRecord getQuietNotification() {
148        return getNotificationRecord(mId, false /* insistent */, false /* once */,
149                false /* noisy */, false /* buzzy*/, false /* lights */);
150    }
151
152    private NotificationRecord getQuietOtherNotification() {
153        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
154                false /* noisy */, false /* buzzy*/, false /* lights */);
155    }
156
157    private NotificationRecord getQuietOnceNotification() {
158        return getNotificationRecord(mId, false /* insistent */, true /* once */,
159                false /* noisy */, false /* buzzy*/, false /* lights */);
160    }
161
162    private NotificationRecord getInsistentBeepyNotification() {
163        return getNotificationRecord(mId, true /* insistent */, false /* once */,
164                true /* noisy */, false /* buzzy*/, false /* lights */);
165    }
166
167    private NotificationRecord getInsistentBeepyLeanbackNotification() {
168        return getLeanbackNotificationRecord(mId, true /* insistent */, false /* once */,
169                true /* noisy */, false /* buzzy*/, false /* lights */);
170    }
171
172    private NotificationRecord getBuzzyNotification() {
173        return getNotificationRecord(mId, false /* insistent */, false /* once */,
174                false /* noisy */, true /* buzzy*/, false /* lights */);
175    }
176
177    private NotificationRecord getBuzzyOnceNotification() {
178        return getNotificationRecord(mId, false /* insistent */, true /* once */,
179                false /* noisy */, true /* buzzy*/, false /* lights */);
180    }
181
182    private NotificationRecord getInsistentBuzzyNotification() {
183        return getNotificationRecord(mId, true /* insistent */, false /* once */,
184                false /* noisy */, true /* buzzy*/, false /* lights */);
185    }
186
187    private NotificationRecord getBuzzyBeepyNotification() {
188        return getNotificationRecord(mId, false /* insistent */, false /* once */,
189                true /* noisy */, true /* buzzy*/, false /* lights */);
190    }
191
192    private NotificationRecord getLightsNotification() {
193        return getNotificationRecord(mId, false /* insistent */, true /* once */,
194                false /* noisy */, true /* buzzy*/, true /* lights */);
195    }
196
197    private NotificationRecord getCustomLightsNotification() {
198        return getNotificationRecord(mId, false /* insistent */, true /* once */,
199                false /* noisy */, true /* buzzy*/, true /* lights */,
200                true /* defaultVibration */, true /* defaultSound */, false /* defaultLights */,
201                null, Notification.GROUP_ALERT_ALL, false);
202    }
203
204    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
205            boolean noisy, boolean buzzy, boolean lights) {
206        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
207                null, Notification.GROUP_ALERT_ALL, false);
208    }
209
210    private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
211            boolean noisy, boolean buzzy, boolean lights) {
212        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
213                null, Notification.GROUP_ALERT_ALL, true);
214    }
215
216    private NotificationRecord getBeepyNotificationRecord(String groupKey, int groupAlertBehavior) {
217        return getNotificationRecord(mId, false, false, true, false, false, true, true, true,
218                groupKey, groupAlertBehavior, false);
219    }
220
221    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
222            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
223            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
224            boolean isLeanback) {
225        NotificationChannel channel =
226                new NotificationChannel("test", "test", IMPORTANCE_HIGH);
227        final Builder builder = new Builder(getContext())
228                .setContentTitle("foo")
229                .setSmallIcon(android.R.drawable.sym_def_app_icon)
230                .setPriority(Notification.PRIORITY_HIGH)
231                .setOnlyAlertOnce(once);
232
233        int defaults = 0;
234        if (noisy) {
235            if (defaultSound) {
236                defaults |= Notification.DEFAULT_SOUND;
237                channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
238                        Notification.AUDIO_ATTRIBUTES_DEFAULT);
239            } else {
240                builder.setSound(CUSTOM_SOUND);
241                channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
242            }
243        } else {
244            channel.setSound(null, null);
245        }
246        if (buzzy) {
247            if (defaultVibration) {
248                defaults |= Notification.DEFAULT_VIBRATE;
249            } else {
250                builder.setVibrate(CUSTOM_VIBRATION);
251                channel.setVibrationPattern(CUSTOM_VIBRATION);
252            }
253            channel.enableVibration(true);
254        }
255        if (lights) {
256            if (defaultLights) {
257                defaults |= Notification.DEFAULT_LIGHTS;
258            } else {
259                builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
260            }
261            channel.enableLights(true);
262        }
263        builder.setDefaults(defaults);
264
265        builder.setGroup(groupKey);
266        builder.setGroupAlertBehavior(groupAlertBehavior);
267
268        Notification n = builder.build();
269        if (insistent) {
270            n.flags |= Notification.FLAG_INSISTENT;
271        }
272
273        Context context = spy(getContext());
274        PackageManager packageManager = spy(context.getPackageManager());
275        when(context.getPackageManager()).thenReturn(packageManager);
276        when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
277                .thenReturn(isLeanback);
278
279        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
280                mPid, n, mUser, null, System.currentTimeMillis());
281        NotificationRecord r = new NotificationRecord(context, sbn, channel);
282        mService.addNotification(r);
283        return r;
284    }
285
286    //
287    // Convenience functions for interacting with mocks
288    //
289
290    private void verifyNeverBeep() throws RemoteException {
291        verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
292                anyBoolean(), (AudioAttributes) anyObject());
293    }
294
295    private void verifyBeep() throws RemoteException {
296        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
297                eq(true), (AudioAttributes) anyObject());
298    }
299
300    private void verifyBeepLooped() throws RemoteException {
301        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
302                eq(false), (AudioAttributes) anyObject());
303    }
304
305    private void verifyCustomBeep() throws RemoteException {
306        verify(mRingtonePlayer, times(1)).playAsync(eq(CUSTOM_SOUND), (UserHandle) anyObject(),
307                eq(false), (AudioAttributes) anyObject());
308    }
309
310    private void verifyNeverStopAudio() throws RemoteException {
311        verify(mRingtonePlayer, never()).stopAsync();
312    }
313
314    private void verifyStopAudio() throws RemoteException {
315        verify(mRingtonePlayer, times(1)).stopAsync();
316    }
317
318    private void verifyNeverVibrate() {
319        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
320                (AudioAttributes) anyObject());
321    }
322
323    private void verifyVibrate() {
324        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
325                (AudioAttributes) anyObject());
326    }
327
328    private void verifyVibrateLooped() {
329        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
330                (AudioAttributes) anyObject());
331    }
332
333    private void verifyDelayedVibrateLooped() {
334        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
335                argThat(mVibrateLoopMatcher), (AudioAttributes) anyObject());
336    }
337
338    private void verifyStopVibrate() {
339        verify(mVibrator, times(1)).cancel();
340    }
341
342    private void verifyNeverStopVibrate() throws RemoteException {
343        verify(mVibrator, never()).cancel();
344    }
345
346    private void verifyLights() {
347        verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
348    }
349
350    private void verifyCustomLights() {
351        verify(mLight, times(1)).setFlashing(
352                eq(CUSTOM_LIGHT_COLOR), anyInt(), eq(CUSTOM_LIGHT_ON), eq(CUSTOM_LIGHT_OFF));
353    }
354
355    //
356    // Tests
357    //
358
359    @Test
360    public void testLights() throws Exception {
361        NotificationRecord r = getLightsNotification();
362        r.setImportance(NotificationManager.IMPORTANCE_DEFAULT, "for testing");
363
364        mService.buzzBeepBlinkLocked(r);
365
366        verifyLights();
367    }
368
369    @Test
370    public void testBeep() throws Exception {
371        NotificationRecord r = getBeepyNotification();
372
373        mService.buzzBeepBlinkLocked(r);
374
375        verifyBeepLooped();
376        verifyNeverVibrate();
377    }
378
379    @Test
380    public void testBeepInsistently() throws Exception {
381        NotificationRecord r = getInsistentBeepyNotification();
382
383        mService.buzzBeepBlinkLocked(r);
384
385        verifyBeep();
386    }
387
388    @Test
389    public void testNoLeanbackBeep() throws Exception {
390        NotificationRecord r = getInsistentBeepyLeanbackNotification();
391
392        mService.buzzBeepBlinkLocked(r);
393
394        verifyNeverBeep();
395    }
396
397    @Test
398    public void testNoInterruptionForMin() throws Exception {
399        NotificationRecord r = getBeepyNotification();
400        r.setImportance(NotificationManager.IMPORTANCE_MIN, "foo");
401
402        mService.buzzBeepBlinkLocked(r);
403
404        verifyNeverBeep();
405        verifyNeverVibrate();
406    }
407
408    @Test
409    public void testNoInterruptionForIntercepted() throws Exception {
410        NotificationRecord r = getBeepyNotification();
411        r.setIntercepted(true);
412
413        mService.buzzBeepBlinkLocked(r);
414
415        verifyNeverBeep();
416        verifyNeverVibrate();
417    }
418
419    @Test
420    public void testBeepTwice() throws Exception {
421        NotificationRecord r = getBeepyNotification();
422
423        // set up internal state
424        mService.buzzBeepBlinkLocked(r);
425        Mockito.reset(mRingtonePlayer);
426
427        // update should beep
428        r.isUpdate = true;
429        mService.buzzBeepBlinkLocked(r);
430        verifyBeepLooped();
431    }
432
433    @Test
434    public void testHonorAlertOnlyOnceForBeep() throws Exception {
435        NotificationRecord r = getBeepyNotification();
436        NotificationRecord s = getBeepyOnceNotification();
437        s.isUpdate = true;
438
439        // set up internal state
440        mService.buzzBeepBlinkLocked(r);
441        Mockito.reset(mRingtonePlayer);
442
443        // update should not beep
444        mService.buzzBeepBlinkLocked(s);
445        verifyNeverBeep();
446    }
447
448    @Test
449    public void testNoisyUpdateDoesNotCancelAudio() throws Exception {
450        NotificationRecord r = getBeepyNotification();
451
452        mService.buzzBeepBlinkLocked(r);
453        r.isUpdate = true;
454        mService.buzzBeepBlinkLocked(r);
455
456        verifyNeverStopAudio();
457    }
458
459    @Test
460    public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception {
461        NotificationRecord r = getBeepyNotification();
462        NotificationRecord s = getBeepyOnceNotification();
463        s.isUpdate = true;
464
465        mService.buzzBeepBlinkLocked(r);
466        mService.buzzBeepBlinkLocked(s);
467
468        verifyNeverStopAudio();
469    }
470
471    @Test
472    public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception {
473        NotificationRecord r = getBeepyNotification();
474        NotificationRecord s = getQuietNotification();
475        s.isUpdate = true;
476        NotificationRecord other = getNoisyOtherNotification();
477
478        // set up internal state
479        mService.buzzBeepBlinkLocked(r);
480        mService.buzzBeepBlinkLocked(other); // this takes the audio stream
481        Mockito.reset(mRingtonePlayer);
482
483        // should not stop noise, since we no longer own it
484        mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
485        verifyNeverStopAudio();
486    }
487
488    @Test
489    public void testQuietInterloperDoesNotCancelAudio() throws Exception {
490        NotificationRecord r = getBeepyNotification();
491        NotificationRecord other = getQuietOtherNotification();
492
493        // set up internal state
494        mService.buzzBeepBlinkLocked(r);
495        Mockito.reset(mRingtonePlayer);
496
497        // should not stop noise, since it does not own it
498        mService.buzzBeepBlinkLocked(other);
499        verifyNeverStopAudio();
500    }
501
502    @Test
503    public void testQuietUpdateCancelsAudio() throws Exception {
504        NotificationRecord r = getBeepyNotification();
505        NotificationRecord s = getQuietNotification();
506        s.isUpdate = true;
507
508        // set up internal state
509        mService.buzzBeepBlinkLocked(r);
510        Mockito.reset(mRingtonePlayer);
511
512        // quiet update should stop making noise
513        mService.buzzBeepBlinkLocked(s);
514        verifyStopAudio();
515    }
516
517    @Test
518    public void testQuietOnceUpdateCancelsAudio() throws Exception {
519        NotificationRecord r = getBeepyNotification();
520        NotificationRecord s = getQuietOnceNotification();
521        s.isUpdate = true;
522
523        // set up internal state
524        mService.buzzBeepBlinkLocked(r);
525        Mockito.reset(mRingtonePlayer);
526
527        // stop making noise - this is a weird corner case, but quiet should override once
528        mService.buzzBeepBlinkLocked(s);
529        verifyStopAudio();
530    }
531
532    @Test
533    public void testNoDemoteSoundToVibrateIfVibrateGiven() throws Exception {
534        NotificationRecord r = getBuzzyBeepyNotification();
535        assertTrue(r.getSound() != null);
536
537        // the phone is quiet
538        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
539
540        mService.buzzBeepBlinkLocked(r);
541
542        VibrationEffect effect = VibrationEffect.createWaveform(r.getVibration(), -1);
543
544        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
545                eq(effect), (AudioAttributes) anyObject());
546    }
547
548    @Test
549    public void testDemoteSoundToVibrate() throws Exception {
550        NotificationRecord r = getBeepyNotification();
551        assertTrue(r.getSound() != null);
552        assertNull(r.getVibration());
553
554        // the phone is quiet
555        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
556
557        mService.buzzBeepBlinkLocked(r);
558
559        verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
560                eq(FALLBACK_VIBRATION), (AudioAttributes) anyObject());
561        verify(mRingtonePlayer, never()).playAsync
562                (anyObject(), anyObject(), anyBoolean(), anyObject());
563    }
564
565    @Test
566    public void testDemoteInsistentSoundToVibrate() throws Exception {
567        NotificationRecord r = getInsistentBeepyNotification();
568        assertTrue(r.getSound() != null);
569        assertNull(r.getVibration());
570
571        // the phone is quiet
572        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
573        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
574
575        mService.buzzBeepBlinkLocked(r);
576
577        verifyDelayedVibrateLooped();
578    }
579
580    @Test
581    public void testVibrate() throws Exception {
582        NotificationRecord r = getBuzzyNotification();
583
584        mService.buzzBeepBlinkLocked(r);
585
586        verifyNeverBeep();
587        verifyVibrate();
588    }
589
590    @Test
591    public void testInsistentVibrate() throws Exception {
592        NotificationRecord r = getInsistentBuzzyNotification();
593
594        mService.buzzBeepBlinkLocked(r);
595        verifyVibrateLooped();
596    }
597
598    @Test
599    public void testVibrateTwice() throws Exception {
600        NotificationRecord r = getBuzzyNotification();
601
602        // set up internal state
603        mService.buzzBeepBlinkLocked(r);
604        Mockito.reset(mVibrator);
605
606        // update should vibrate
607        r.isUpdate = true;
608        mService.buzzBeepBlinkLocked(r);
609        verifyVibrate();
610    }
611
612    @Test
613    public void testGroupAlertSummarySilenceChild() throws Exception {
614        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
615
616        mService.buzzBeepBlinkLocked(child);
617
618        verifyNeverBeep();
619    }
620
621    @Test
622    public void testGroupAlertSummaryNoSilenceSummary() throws Exception {
623        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
624        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
625
626        mService.buzzBeepBlinkLocked(summary);
627
628        verifyBeepLooped();
629    }
630
631    @Test
632    public void testGroupAlertSummaryNoSilenceNonGroupChild() throws Exception {
633        NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_SUMMARY);
634
635        mService.buzzBeepBlinkLocked(nonGroup);
636
637        verifyBeepLooped();
638    }
639
640    @Test
641    public void testGroupAlertChildSilenceSummary() throws Exception {
642        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
643        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
644
645        mService.buzzBeepBlinkLocked(summary);
646
647        verifyNeverBeep();
648    }
649
650    @Test
651    public void testGroupAlertChildNoSilenceChild() throws Exception {
652        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
653
654        mService.buzzBeepBlinkLocked(child);
655
656        verifyBeepLooped();
657    }
658
659    @Test
660    public void testGroupAlertChildNoSilenceNonGroupSummary() throws Exception {
661        NotificationRecord nonGroup = getBeepyNotificationRecord(null, GROUP_ALERT_CHILDREN);
662
663        mService.buzzBeepBlinkLocked(nonGroup);
664
665        verifyBeepLooped();
666    }
667
668    @Test
669    public void testGroupAlertAllNoSilenceGroup() throws Exception {
670        NotificationRecord group = getBeepyNotificationRecord("a", GROUP_ALERT_ALL);
671
672        mService.buzzBeepBlinkLocked(group);
673
674        verifyBeepLooped();
675    }
676
677    @Test
678    public void testHonorAlertOnlyOnceForBuzz() throws Exception {
679        NotificationRecord r = getBuzzyNotification();
680        NotificationRecord s = getBuzzyOnceNotification();
681        s.isUpdate = true;
682
683        // set up internal state
684        mService.buzzBeepBlinkLocked(r);
685        Mockito.reset(mVibrator);
686
687        // update should not beep
688        mService.buzzBeepBlinkLocked(s);
689        verifyNeverVibrate();
690    }
691
692    @Test
693    public void testNoisyUpdateDoesNotCancelVibrate() throws Exception {
694        NotificationRecord r = getBuzzyNotification();
695
696        mService.buzzBeepBlinkLocked(r);
697        r.isUpdate = true;
698        mService.buzzBeepBlinkLocked(r);
699
700        verifyNeverStopVibrate();
701    }
702
703    @Test
704    public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception {
705        NotificationRecord r = getBuzzyNotification();
706        NotificationRecord s = getBuzzyOnceNotification();
707        s.isUpdate = true;
708
709        mService.buzzBeepBlinkLocked(r);
710        mService.buzzBeepBlinkLocked(s);
711
712        verifyNeverStopVibrate();
713    }
714
715    @Test
716    public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception {
717        NotificationRecord r = getBuzzyNotification();
718        NotificationRecord s = getQuietNotification();
719        s.isUpdate = true;
720        NotificationRecord other = getNoisyOtherNotification();
721
722        // set up internal state
723        mService.buzzBeepBlinkLocked(r);
724        mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream
725        Mockito.reset(mVibrator);
726
727        // should not stop vibrate, since we no longer own it
728        mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
729        verifyNeverStopVibrate();
730    }
731
732    @Test
733    public void testQuietInterloperDoesNotCancelVibrate() throws Exception {
734        NotificationRecord r = getBuzzyNotification();
735        NotificationRecord other = getQuietOtherNotification();
736
737        // set up internal state
738        mService.buzzBeepBlinkLocked(r);
739        Mockito.reset(mVibrator);
740
741        // should not stop noise, since it does not own it
742        mService.buzzBeepBlinkLocked(other);
743        verifyNeverStopVibrate();
744    }
745
746    @Test
747    public void testQuietUpdateCancelsVibrate() throws Exception {
748        NotificationRecord r = getBuzzyNotification();
749        NotificationRecord s = getQuietNotification();
750        s.isUpdate = true;
751
752        // set up internal state
753        mService.buzzBeepBlinkLocked(r);
754        verifyVibrate();
755
756        // quiet update should stop making noise
757        mService.buzzBeepBlinkLocked(s);
758        verifyStopVibrate();
759    }
760
761    @Test
762    public void testQuietOnceUpdateCancelVibrate() throws Exception {
763        NotificationRecord r = getBuzzyNotification();
764        NotificationRecord s = getQuietOnceNotification();
765        s.isUpdate = true;
766
767        // set up internal state
768        mService.buzzBeepBlinkLocked(r);
769        verifyVibrate();
770
771        // stop making noise - this is a weird corner case, but quiet should override once
772        mService.buzzBeepBlinkLocked(s);
773        verifyStopVibrate();
774    }
775
776    @Test
777    public void testQuietUpdateCancelsDemotedVibrate() throws Exception {
778        NotificationRecord r = getBeepyNotification();
779        NotificationRecord s = getQuietNotification();
780
781        // the phone is quiet
782        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
783        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
784
785        mService.buzzBeepBlinkLocked(r);
786
787        // quiet update should stop making noise
788        mService.buzzBeepBlinkLocked(s);
789        verifyStopVibrate();
790    }
791
792    @Test
793    public void testEmptyUriSoundTreatedAsNoSound() throws Exception {
794        NotificationChannel channel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
795        channel.setSound(Uri.EMPTY, null);
796        final Notification n = new Builder(getContext(), "test")
797                .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
798
799        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
800                mPid, n, mUser, null, System.currentTimeMillis());
801        NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
802        mService.addNotification(r);
803
804        mService.buzzBeepBlinkLocked(r);
805
806        verifyNeverBeep();
807    }
808
809    static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
810        private final int mRepeatIndex;
811
812        VibrateRepeatMatcher(int repeatIndex) {
813            mRepeatIndex = repeatIndex;
814        }
815
816        @Override
817        public boolean matches(VibrationEffect actual) {
818            if (actual instanceof VibrationEffect.Waveform &&
819                    ((VibrationEffect.Waveform) actual).getRepeatIndex() == mRepeatIndex) {
820                return true;
821            }
822            // All non-waveform effects are essentially one shots.
823            return mRepeatIndex == -1;
824        }
825
826        @Override
827        public String toString() {
828            return "repeatIndex=" + mRepeatIndex;
829        }
830    }
831}
832