1 /*
2  * Copyright (C) 2020 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.settings.slices;
18 
19 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SLICES_URI;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.Mockito.never;
24 import static org.mockito.Mockito.spy;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.content.BroadcastReceiver;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.media.AudioManager;
34 import android.net.Uri;
35 
36 import com.android.settings.notification.MediaVolumePreferenceController;
37 import com.android.settings.notification.NotificationVolumePreferenceController;
38 import com.android.settings.notification.RingVolumePreferenceController;
39 import com.android.settings.notification.SeparateRingVolumePreferenceController;
40 import com.android.settings.notification.VolumeSeekBarPreferenceController;
41 import com.android.settingslib.SliceBroadcastRelay;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Test;
46 import org.junit.runner.RunWith;
47 import org.mockito.Mock;
48 import org.mockito.MockitoAnnotations;
49 import org.robolectric.RobolectricTestRunner;
50 import org.robolectric.RuntimeEnvironment;
51 import org.robolectric.annotation.Config;
52 import org.robolectric.annotation.Implementation;
53 import org.robolectric.annotation.Implements;
54 import org.robolectric.annotation.Resetter;
55 
56 @RunWith(RobolectricTestRunner.class)
57 @Config(shadows = VolumeSliceHelperTest.ShadowSliceBroadcastRelay.class)
58 public class VolumeSliceHelperTest {
59 
60     @Mock
61     private ContentResolver mResolver;
62 
63     private Context mContext;
64     private Intent mIntent;
65     private VolumeSeekBarPreferenceController mMediaController;
66     private VolumeSeekBarPreferenceController mRingController;
67     private VolumeSeekBarPreferenceController mSeparateRingController;
68     private VolumeSeekBarPreferenceController mNotificationController;
69 
70     @Before
setUp()71     public void setUp() {
72         MockitoAnnotations.initMocks(this);
73         mContext = spy(RuntimeEnvironment.application);
74         when(mContext.getContentResolver()).thenReturn(mResolver);
75 
76         mMediaController = new MediaVolumePreferenceController(mContext);
77         mRingController = new RingVolumePreferenceController(mContext);
78         mSeparateRingController = new SeparateRingVolumePreferenceController(mContext);
79         mNotificationController = new NotificationVolumePreferenceController(mContext);
80 
81         mIntent = createIntent(AudioManager.VOLUME_CHANGED_ACTION)
82                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 1)
83                 .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 2)
84                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mMediaController.getAudioStream());
85     }
86 
87     @After
cleanUp()88     public void cleanUp() {
89         ShadowSliceBroadcastRelay.reset();
90         VolumeSliceHelper.sRegisteredUri.clear();
91         VolumeSliceHelper.sIntentFilter = null;
92     }
93 
94     @Test
registerIntentToUri_volumeController_shouldRegisterReceiver()95     public void registerIntentToUri_volumeController_shouldRegisterReceiver() {
96         registerIntentToUri(mMediaController);
97 
98         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(1);
99         assertThat(VolumeSliceHelper.sRegisteredUri)
100                 .containsKey((mMediaController.getSliceUri()));
101     }
102 
103     @Test
registerIntentToUri_doubleVolumeControllers_shouldRegisterReceiverOnce()104     public void registerIntentToUri_doubleVolumeControllers_shouldRegisterReceiverOnce() {
105         registerIntentToUri(mMediaController);
106 
107         registerIntentToUri(mRingController);
108 
109         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(1);
110         assertThat(VolumeSliceHelper.sRegisteredUri)
111                 .containsKey((mRingController.getSliceUri()));
112     }
113 
114     @Test
unregisterUri_notFinalUri_shouldNotUnregisterReceiver()115     public void unregisterUri_notFinalUri_shouldNotUnregisterReceiver() {
116         registerIntentToUri(mMediaController);
117         registerIntentToUri(mRingController);
118 
119         VolumeSliceHelper.unregisterUri(mContext, mMediaController.getSliceUri());
120 
121         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(1);
122         assertThat(VolumeSliceHelper.sRegisteredUri)
123                 .doesNotContainKey((mMediaController.getSliceUri()));
124     }
125 
126     @Test
unregisterUri_finalUri_shouldUnregisterReceiver()127     public void unregisterUri_finalUri_shouldUnregisterReceiver() {
128         registerIntentToUri(mMediaController);
129 
130         VolumeSliceHelper.unregisterUri(mContext, mMediaController.getSliceUri());
131 
132         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(0);
133         assertThat(VolumeSliceHelper.sRegisteredUri)
134                 .doesNotContainKey((mMediaController.getSliceUri()));
135     }
136 
137     @Test
unregisterUri_unregisterTwice_shouldUnregisterReceiverOnce()138     public void unregisterUri_unregisterTwice_shouldUnregisterReceiverOnce() {
139         registerIntentToUri(mMediaController);
140 
141         VolumeSliceHelper.unregisterUri(mContext, mMediaController.getSliceUri());
142         VolumeSliceHelper.unregisterUri(mContext, mMediaController.getSliceUri());
143 
144         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(0);
145     }
146 
147     @Test
unregisterUri_notRegistered_shouldNotUnregisterReceiver()148     public void unregisterUri_notRegistered_shouldNotUnregisterReceiver() {
149         registerIntentToUri(mMediaController);
150 
151         VolumeSliceHelper.unregisterUri(mContext, mRingController.getSliceUri());
152 
153         assertThat(ShadowSliceBroadcastRelay.getRegisteredCount()).isEqualTo(1);
154         assertThat(VolumeSliceHelper.sRegisteredUri)
155                 .containsKey((mMediaController.getSliceUri()));
156     }
157 
158     @Test
onReceive_audioStreamRegistered_shouldNotifyChange()159     public void onReceive_audioStreamRegistered_shouldNotifyChange() {
160         registerIntentToUri(mMediaController);
161 
162         VolumeSliceHelper.onReceive(mContext, mIntent);
163 
164         verify(mResolver).notifyChange(mMediaController.getSliceUri(), null);
165     }
166 
167     @Test
onReceive_audioStreamNotRegistered_shouldNotNotifyChange()168     public void onReceive_audioStreamNotRegistered_shouldNotNotifyChange() {
169         VolumeSliceHelper.onReceive(mContext, mIntent);
170 
171         verify(mResolver, never()).notifyChange(mMediaController.getSliceUri(), null);
172     }
173 
174     @Test
onReceive_audioStreamNotMatched_shouldNotNotifyChange()175     public void onReceive_audioStreamNotMatched_shouldNotNotifyChange() {
176         registerIntentToUri(mMediaController);
177         mIntent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_DTMF);
178 
179         VolumeSliceHelper.onReceive(mContext, mIntent);
180 
181         verify(mResolver, never()).notifyChange(mMediaController.getSliceUri(), null);
182     }
183 
184     @Test
onReceive_mediaVolumeNotChanged_shouldNotNotifyChange()185     public void onReceive_mediaVolumeNotChanged_shouldNotNotifyChange() {
186         mIntent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 1)
187                 .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 1);
188         registerIntentToUri(mMediaController);
189 
190         VolumeSliceHelper.onReceive(mContext, mIntent);
191 
192         verify(mResolver, never()).notifyChange(mMediaController.getSliceUri(), null);
193     }
194 
195     @Test
onReceive_ringStreamVolumeMuted_shouldNotifySeparateRing()196     public void onReceive_ringStreamVolumeMuted_shouldNotifySeparateRing() {
197         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
198                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
199         registerIntentToUri(mRingController);
200         registerIntentToUri(mSeparateRingController);
201 
202         VolumeSliceHelper.onReceive(mContext, intent);
203 
204         verify(mResolver).notifyChange(mSeparateRingController.getSliceUri(), null);
205     }
206 
207     @Test
onReceive_ringStreamVolumeMuted_shouldNotifyRing()208     public void onReceive_ringStreamVolumeMuted_shouldNotifyRing() {
209         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
210                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
211         registerIntentToUri(mRingController);
212         registerIntentToUri(mSeparateRingController);
213 
214         VolumeSliceHelper.onReceive(mContext, intent);
215 
216         verify(mResolver).notifyChange(mRingController.getSliceUri(), null);
217     }
218 
219     @Test
onReceive_ringStreamVolumeMuted_shouldNotifyBothRings()220     public void onReceive_ringStreamVolumeMuted_shouldNotifyBothRings() {
221         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
222                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
223         registerIntentToUri(mRingController);
224         registerIntentToUri(mSeparateRingController);
225 
226         VolumeSliceHelper.onReceive(mContext, intent);
227 
228         verify(mResolver).notifyChange(mSeparateRingController.getSliceUri(), null);
229         verify(mResolver).notifyChange(mRingController.getSliceUri(), null);
230     }
231 
232     @Test
onReceive_streamVolumeMuted_shouldNotifyChange()233     public void onReceive_streamVolumeMuted_shouldNotifyChange() {
234         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
235                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mMediaController.getAudioStream());
236         registerIntentToUri(mMediaController);
237         registerIntentToUri(mRingController);
238 
239         VolumeSliceHelper.onReceive(mContext, intent);
240 
241         verify(mResolver).notifyChange(mMediaController.getSliceUri(), null);
242     }
243 
244     /**
245      * Without this test passing, when notification is separated from ring and its value is already
246      * zero, setting ringermode to silent would not disable notification slider.
247      * Note: the above scenario happens only in volume panel where controllers do not get to
248      * register for events such as RINGER_MODE_CHANGE.
249      */
250     @Test
onReceive_ringVolumeMuted_shouldNotifyChangeNotificationSlice()251     public void onReceive_ringVolumeMuted_shouldNotifyChangeNotificationSlice() {
252         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
253                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
254         registerIntentToUri(mRingController);
255         registerIntentToUri(mNotificationController);
256 
257         VolumeSliceHelper.onReceive(mContext, intent);
258 
259         verify(mResolver).notifyChange(mNotificationController.getSliceUri(), null);
260     }
261 
262     /**
263      * Notifying notification slice on ring mute does not mean it should not notify ring slice.
264      * Rather, it should notify both slices.
265      */
266     @Test
onReceive_ringVolumeMuted_shouldNotifyChangeRingSlice()267     public void onReceive_ringVolumeMuted_shouldNotifyChangeRingSlice() {
268         final Intent intent = createIntent(AudioManager.STREAM_MUTE_CHANGED_ACTION)
269                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
270         registerIntentToUri(mRingController);
271         registerIntentToUri(mNotificationController);
272 
273         VolumeSliceHelper.onReceive(mContext, intent);
274 
275         verify(mResolver).notifyChange(mRingController.getSliceUri(), null);
276     }
277 
278     @Test
onReceive_streamDevicesChanged_shouldNotifyChange()279     public void onReceive_streamDevicesChanged_shouldNotifyChange() {
280         final Intent intent = createIntent(AudioManager.STREAM_DEVICES_CHANGED_ACTION)
281                 .putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mRingController.getAudioStream());
282         registerIntentToUri(mMediaController);
283         registerIntentToUri(mRingController);
284 
285         VolumeSliceHelper.onReceive(mContext, intent);
286 
287         verify(mResolver).notifyChange(mRingController.getSliceUri(), null);
288     }
289 
290     @Test
onReceive_primaryMutedChanged_shouldNotifyChangeAll()291     public void onReceive_primaryMutedChanged_shouldNotifyChangeAll() {
292         final Intent intent = createIntent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
293         registerIntentToUri(mMediaController);
294         registerIntentToUri(mRingController);
295 
296         VolumeSliceHelper.onReceive(mContext, intent);
297 
298         verify(mResolver).notifyChange(mMediaController.getSliceUri(), null);
299         verify(mResolver).notifyChange(mRingController.getSliceUri(), null);
300     }
301 
registerIntentToUri(VolumeSeekBarPreferenceController controller)302     private void registerIntentToUri(VolumeSeekBarPreferenceController controller) {
303         VolumeSliceHelper.registerIntentToUri(mContext, controller.getIntentFilter(),
304                 controller.getSliceUri(), controller.getAudioStream());
305     }
306 
createIntent(String action)307     private Intent createIntent(String action) {
308         return new Intent(action)
309                 .putExtra(SliceBroadcastRelay.EXTRA_URI, VOLUME_SLICES_URI.toString());
310     }
311 
312     @Implements(SliceBroadcastRelay.class)
313     public static class ShadowSliceBroadcastRelay {
314 
315         private static int sRegisteredCount;
316 
317         @Implementation
registerReceiver(Context context, Uri sliceUri, Class<? extends BroadcastReceiver> receiver, IntentFilter filter)318         public static void registerReceiver(Context context, Uri sliceUri,
319                 Class<? extends BroadcastReceiver> receiver, IntentFilter filter) {
320             sRegisteredCount++;
321         }
322 
323         @Implementation
unregisterReceivers(Context context, Uri sliceUri)324         public static void unregisterReceivers(Context context, Uri sliceUri) {
325             sRegisteredCount--;
326         }
327 
328         @Resetter
reset()329         static void reset() {
330             sRegisteredCount = 0;
331         }
332 
getRegisteredCount()333         static int getRegisteredCount() {
334             return sRegisteredCount;
335         }
336     }
337 }
338