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
18
19import android.app.ActivityManager;
20import android.app.Notification;
21import android.app.Notification.Builder;
22import android.media.AudioAttributes;
23import android.media.AudioManager;
24import android.net.Uri;
25import android.os.Handler;
26import android.os.RemoteException;
27import android.os.UserHandle;
28import android.os.Vibrator;
29import android.service.notification.NotificationListenerService.Ranking;
30import android.service.notification.StatusBarNotification;
31import android.test.AndroidTestCase;
32import android.test.suitebuilder.annotation.SmallTest;
33
34import org.mockito.Mock;
35import org.mockito.Mockito;
36import org.mockito.MockitoAnnotations;
37
38import static org.mockito.Matchers.anyBoolean;
39import static org.mockito.Matchers.anyInt;
40import static org.mockito.Matchers.anyObject;
41import static org.mockito.Matchers.anyString;
42import static org.mockito.Matchers.eq;
43import static org.mockito.Mockito.never;
44import static org.mockito.Mockito.times;
45import static org.mockito.Mockito.verify;
46import static org.mockito.Mockito.when;
47
48public class BuzzBeepBlinkTest extends AndroidTestCase {
49
50    @Mock AudioManager mAudioManager;
51    @Mock Vibrator mVibrator;
52    @Mock android.media.IRingtonePlayer mRingtonePlayer;
53    @Mock Handler mHandler;
54
55    private NotificationManagerService mService;
56    private String mPkg = "com.android.server.notification";
57    private int mId = 1001;
58    private int mOtherId = 1002;
59    private String mTag = null;
60    private int mUid = 1000;
61    private int mPid = 2000;
62    private int mScore = 10;
63    private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
64
65    @Override
66    public void setUp() {
67        MockitoAnnotations.initMocks(this);
68
69        when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
70        when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
71        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(10);
72        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
73
74        mService = new NotificationManagerService(getContext());
75        mService.setAudioManager(mAudioManager);
76        mService.setVibrator(mVibrator);
77        mService.setSystemReady(true);
78        mService.setHandler(mHandler);
79    }
80
81    //
82    // Convenience functions for creating notification records
83    //
84
85    private NotificationRecord getNoisyOtherNotification() {
86        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
87                true /* noisy */, true /* buzzy*/);
88    }
89
90    private NotificationRecord getBeepyNotification() {
91        return getNotificationRecord(mId, false /* insistent */, false /* once */,
92                true /* noisy */, false /* buzzy*/);
93    }
94
95    private NotificationRecord getBeepyOnceNotification() {
96        return getNotificationRecord(mId, false /* insistent */, true /* once */,
97                true /* noisy */, false /* buzzy*/);
98    }
99
100    private NotificationRecord getQuietNotification() {
101        return getNotificationRecord(mId, false /* insistent */, false /* once */,
102                false /* noisy */, false /* buzzy*/);
103    }
104
105    private NotificationRecord getQuietOtherNotification() {
106        return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
107                false /* noisy */, false /* buzzy*/);
108    }
109
110    private NotificationRecord getQuietOnceNotification() {
111        return getNotificationRecord(mId, false /* insistent */, true /* once */,
112                false /* noisy */, false /* buzzy*/);
113    }
114
115    private NotificationRecord getInsistentBeepyNotification() {
116        return getNotificationRecord(mId, true /* insistent */, false /* once */,
117                true /* noisy */, false /* buzzy*/);
118    }
119
120    private NotificationRecord getBuzzyNotification() {
121        return getNotificationRecord(mId, false /* insistent */, false /* once */,
122                false /* noisy */, true /* buzzy*/);
123    }
124
125    private NotificationRecord getBuzzyOnceNotification() {
126        return getNotificationRecord(mId, false /* insistent */, true /* once */,
127                false /* noisy */, true /* buzzy*/);
128    }
129
130    private NotificationRecord getInsistentBuzzyNotification() {
131        return getNotificationRecord(mId, true /* insistent */, false /* once */,
132                false /* noisy */, true /* buzzy*/);
133    }
134
135    private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
136            boolean noisy, boolean buzzy) {
137        final Builder builder = new Builder(getContext())
138                .setContentTitle("foo")
139                .setSmallIcon(android.R.drawable.sym_def_app_icon)
140                .setPriority(Notification.PRIORITY_HIGH)
141                .setOnlyAlertOnce(once);
142
143        int defaults = 0;
144        if (noisy) {
145            defaults |= Notification.DEFAULT_SOUND;
146        }
147        if (buzzy) {
148            defaults |= Notification.DEFAULT_VIBRATE;
149        }
150        builder.setDefaults(defaults);
151
152        Notification n = builder.build();
153        if (insistent) {
154            n.flags |= Notification.FLAG_INSISTENT;
155        }
156        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid, mPid,
157                mScore, n, mUser, System.currentTimeMillis());
158        return new NotificationRecord(getContext(), sbn);
159    }
160
161    //
162    // Convenience functions for interacting with mocks
163    //
164
165    private void verifyNeverBeep() throws RemoteException {
166        verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
167                anyBoolean(), (AudioAttributes) anyObject());
168    }
169
170    private void verifyBeep() throws RemoteException {
171        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
172                eq(true), (AudioAttributes) anyObject());
173    }
174
175    private void verifyBeepLooped() throws RemoteException {
176        verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
177                eq(false), (AudioAttributes) anyObject());
178    }
179
180    private void verifyNeverStopAudio() throws RemoteException {
181        verify(mRingtonePlayer, never()).stopAsync();
182    }
183
184    private void verifyStopAudio() throws RemoteException {
185        verify(mRingtonePlayer, times(1)).stopAsync();
186    }
187
188    private void verifyNeverVibrate() {
189        verify(mVibrator, never()).vibrate(anyInt(), anyString(), (long[]) anyObject(),
190                anyInt(), (AudioAttributes) anyObject());
191    }
192
193    private void verifyVibrate() {
194        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
195                eq(-1), (AudioAttributes) anyObject());
196    }
197
198    private void verifyVibrateLooped() {
199        verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), (long[]) anyObject(),
200                eq(0), (AudioAttributes) anyObject());
201    }
202
203    private void verifyStopVibrate() {
204        verify(mVibrator, times(1)).cancel();
205    }
206
207    private void verifyNeverStopVibrate() throws RemoteException {
208        verify(mVibrator, never()).cancel();
209    }
210
211    @SmallTest
212    public void testBeep() throws Exception {
213        NotificationRecord r = getBeepyNotification();
214
215        mService.buzzBeepBlinkLocked(r);
216
217        verifyBeepLooped();
218        verifyNeverVibrate();
219    }
220
221    //
222    // Tests
223    //
224
225    @SmallTest
226    public void testBeepInsistently() throws Exception {
227        NotificationRecord r = getInsistentBeepyNotification();
228
229        mService.buzzBeepBlinkLocked(r);
230
231        verifyBeep();
232    }
233
234    @SmallTest
235    public void testNoInterruptionForMin() throws Exception {
236        NotificationRecord r = getBeepyNotification();
237        r.setImportance(Ranking.IMPORTANCE_MIN, "foo");
238
239        mService.buzzBeepBlinkLocked(r);
240
241        verifyNeverBeep();
242        verifyNeverVibrate();
243    }
244
245    @SmallTest
246    public void testNoInterruptionForIntercepted() throws Exception {
247        NotificationRecord r = getBeepyNotification();
248        r.setIntercepted(true);
249
250        mService.buzzBeepBlinkLocked(r);
251
252        verifyNeverBeep();
253        verifyNeverVibrate();
254    }
255
256    @SmallTest
257    public void testBeepTwice() throws Exception {
258        NotificationRecord r = getBeepyNotification();
259
260        // set up internal state
261        mService.buzzBeepBlinkLocked(r);
262        Mockito.reset(mRingtonePlayer);
263
264        // update should beep
265        r.isUpdate = true;
266        mService.buzzBeepBlinkLocked(r);
267        verifyBeepLooped();
268    }
269
270    @SmallTest
271    public void testHonorAlertOnlyOnceForBeep() throws Exception {
272        NotificationRecord r = getBeepyNotification();
273        NotificationRecord s = getBeepyOnceNotification();
274        s.isUpdate = true;
275
276        // set up internal state
277        mService.buzzBeepBlinkLocked(r);
278        Mockito.reset(mRingtonePlayer);
279
280        // update should not beep
281        mService.buzzBeepBlinkLocked(s);
282        verifyNeverBeep();
283    }
284
285    @SmallTest
286    public void testNoisyUpdateDoesNotCancelAudio() throws Exception {
287        NotificationRecord r = getBeepyNotification();
288
289        mService.buzzBeepBlinkLocked(r);
290        r.isUpdate = true;
291        mService.buzzBeepBlinkLocked(r);
292
293        verifyNeverStopAudio();
294    }
295
296    @SmallTest
297    public void testNoisyOnceUpdateDoesNotCancelAudio() throws Exception {
298        NotificationRecord r = getBeepyNotification();
299        NotificationRecord s = getBeepyOnceNotification();
300        s.isUpdate = true;
301
302        mService.buzzBeepBlinkLocked(r);
303        mService.buzzBeepBlinkLocked(s);
304
305        verifyNeverStopAudio();
306    }
307
308    @SmallTest
309    public void testQuietUpdateDoesNotCancelAudioFromOther() throws Exception {
310        NotificationRecord r = getBeepyNotification();
311        NotificationRecord s = getQuietNotification();
312        s.isUpdate = true;
313        NotificationRecord other = getNoisyOtherNotification();
314
315        // set up internal state
316        mService.buzzBeepBlinkLocked(r);
317        mService.buzzBeepBlinkLocked(other); // this takes the audio stream
318        Mockito.reset(mRingtonePlayer);
319
320        // should not stop noise, since we no longer own it
321        mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
322        verifyNeverStopAudio();
323    }
324
325    @SmallTest
326    public void testQuietInterloperDoesNotCancelAudio() throws Exception {
327        NotificationRecord r = getBeepyNotification();
328        NotificationRecord other = getQuietOtherNotification();
329
330        // set up internal state
331        mService.buzzBeepBlinkLocked(r);
332        Mockito.reset(mRingtonePlayer);
333
334        // should not stop noise, since it does not own it
335        mService.buzzBeepBlinkLocked(other);
336        verifyNeverStopAudio();
337    }
338
339    @SmallTest
340    public void testQuietUpdateCancelsAudio() throws Exception {
341        NotificationRecord r = getBeepyNotification();
342        NotificationRecord s = getQuietNotification();
343        s.isUpdate = true;
344
345        // set up internal state
346        mService.buzzBeepBlinkLocked(r);
347        Mockito.reset(mRingtonePlayer);
348
349        // quiet update should stop making noise
350        mService.buzzBeepBlinkLocked(s);
351        verifyStopAudio();
352    }
353
354    @SmallTest
355    public void testQuietOnceUpdateCancelsAudio() throws Exception {
356        NotificationRecord r = getBeepyNotification();
357        NotificationRecord s = getQuietOnceNotification();
358        s.isUpdate = true;
359
360        // set up internal state
361        mService.buzzBeepBlinkLocked(r);
362        Mockito.reset(mRingtonePlayer);
363
364        // stop making noise - this is a weird corner case, but quiet should override once
365        mService.buzzBeepBlinkLocked(s);
366        verifyStopAudio();
367    }
368
369    @SmallTest
370    public void testDemoteSoundToVibrate() throws Exception {
371        NotificationRecord r = getBeepyNotification();
372
373        // the phone is quiet
374        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
375        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
376
377        mService.buzzBeepBlinkLocked(r);
378
379        verifyNeverBeep();
380        verifyVibrate();
381    }
382
383    @SmallTest
384    public void testDemotInsistenteSoundToVibrate() throws Exception {
385        NotificationRecord r = getInsistentBeepyNotification();
386
387        // the phone is quiet
388        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
389        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
390
391        mService.buzzBeepBlinkLocked(r);
392
393        verifyVibrateLooped();
394    }
395
396    @SmallTest
397    public void testVibrate() throws Exception {
398        NotificationRecord r = getBuzzyNotification();
399
400        mService.buzzBeepBlinkLocked(r);
401
402        verifyNeverBeep();
403        verifyVibrate();
404    }
405
406    @SmallTest
407    public void testInsistenteVibrate() throws Exception {
408        NotificationRecord r = getInsistentBuzzyNotification();
409
410        mService.buzzBeepBlinkLocked(r);
411        verifyVibrateLooped();
412    }
413
414    @SmallTest
415    public void testVibratTwice() throws Exception {
416        NotificationRecord r = getBuzzyNotification();
417
418        // set up internal state
419        mService.buzzBeepBlinkLocked(r);
420        Mockito.reset(mVibrator);
421
422        // update should vibrate
423        r.isUpdate = true;
424        mService.buzzBeepBlinkLocked(r);
425        verifyVibrate();
426    }
427
428    @SmallTest
429    public void testHonorAlertOnlyOnceForBuzz() throws Exception {
430        NotificationRecord r = getBuzzyNotification();
431        NotificationRecord s = getBuzzyOnceNotification();
432        s.isUpdate = true;
433
434        // set up internal state
435        mService.buzzBeepBlinkLocked(r);
436        Mockito.reset(mVibrator);
437
438        // update should not beep
439        mService.buzzBeepBlinkLocked(s);
440        verifyNeverVibrate();
441    }
442
443    @SmallTest
444    public void testNoisyUpdateDoesNotCancelVibrate() throws Exception {
445        NotificationRecord r = getBuzzyNotification();
446
447        mService.buzzBeepBlinkLocked(r);
448        r.isUpdate = true;
449        mService.buzzBeepBlinkLocked(r);
450
451        verifyNeverStopVibrate();
452    }
453
454    @SmallTest
455    public void testNoisyOnceUpdateDoesNotCancelVibrate() throws Exception {
456        NotificationRecord r = getBuzzyNotification();
457        NotificationRecord s = getBuzzyOnceNotification();
458        s.isUpdate = true;
459
460        mService.buzzBeepBlinkLocked(r);
461        mService.buzzBeepBlinkLocked(s);
462
463        verifyNeverStopVibrate();
464    }
465
466    @SmallTest
467    public void testQuietUpdateDoesNotCancelVibrateFromOther() throws Exception {
468        NotificationRecord r = getBuzzyNotification();
469        NotificationRecord s = getQuietNotification();
470        s.isUpdate = true;
471        NotificationRecord other = getNoisyOtherNotification();
472
473        // set up internal state
474        mService.buzzBeepBlinkLocked(r);
475        mService.buzzBeepBlinkLocked(other); // this takes the vibrate stream
476        Mockito.reset(mVibrator);
477
478        // should not stop vibrate, since we no longer own it
479        mService.buzzBeepBlinkLocked(s); // this no longer owns the stream
480        verifyNeverStopVibrate();
481    }
482
483    @SmallTest
484    public void testQuietInterloperDoesNotCancelVibrate() throws Exception {
485        NotificationRecord r = getBuzzyNotification();
486        NotificationRecord other = getQuietOtherNotification();
487
488        // set up internal state
489        mService.buzzBeepBlinkLocked(r);
490        Mockito.reset(mVibrator);
491
492        // should not stop noise, since it does not own it
493        mService.buzzBeepBlinkLocked(other);
494        verifyNeverStopVibrate();
495    }
496
497    @SmallTest
498    public void testQuietUpdateCancelsVibrate() throws Exception {
499        NotificationRecord r = getBuzzyNotification();
500        NotificationRecord s = getQuietNotification();
501        s.isUpdate = true;
502
503        // set up internal state
504        mService.buzzBeepBlinkLocked(r);
505
506        // quiet update should stop making noise
507        mService.buzzBeepBlinkLocked(s);
508        verifyStopVibrate();
509    }
510
511    @SmallTest
512    public void testQuietOnceUpdateCancelsvibrate() throws Exception {
513        NotificationRecord r = getBuzzyNotification();
514        NotificationRecord s = getQuietOnceNotification();
515        s.isUpdate = true;
516
517        // set up internal state
518        mService.buzzBeepBlinkLocked(r);
519        Mockito.reset(mVibrator);
520
521        // stop making noise - this is a weird corner case, but quiet should override once
522        mService.buzzBeepBlinkLocked(s);
523        verifyStopVibrate();
524    }
525
526    @SmallTest
527    public void testQuietUpdateCancelsDemotedVibrate() throws Exception {
528        NotificationRecord r = getBeepyNotification();
529        NotificationRecord s = getQuietNotification();
530
531        // the phone is quiet
532        when(mAudioManager.getStreamVolume(anyInt())).thenReturn(0);
533        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
534
535        mService.buzzBeepBlinkLocked(r);
536
537        // quiet update should stop making noise
538        mService.buzzBeepBlinkLocked(s);
539        verifyStopVibrate();
540    }
541}
542