• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.media.audio.cts;
18 
19 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
20 import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
21 import static android.media.AudioAttributes.USAGE_MEDIA;
22 import static android.media.AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE;
23 import static android.media.AudioManager.STREAM_MUSIC;
24 import static android.media.cts.AudioHelper.hasAudioSilentProperty;
25 
26 import android.Manifest;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.media.AudioAttributes;
30 import android.media.AudioManager;
31 import android.media.IVolumeController;
32 import android.media.SoundPool;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import com.android.compatibility.common.util.CtsAndroidTestCase;
37 import com.android.compatibility.common.util.NonMainlineTest;
38 
39 import java.util.concurrent.atomic.AtomicInteger;
40 
41 @NonMainlineTest
42 public class SoundDoseHelperTest extends CtsAndroidTestCase {
43     private static final String TAG = "SoundDoseHelperTest";
44 
45     private static final int TEST_TIMING_TOLERANCE_MS = 100;
46     private static final int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
47     private static final int TEST_MAX_TIME_EXPOSURE_WARNING_MS = 2500;
48 
49     private static final float DEFAULT_RS2_VALUE = 100.f;
50     private static final float MIN_RS2_VALUE = 80.f;
51     private static final float[] CUSTOM_VALID_RS2 = {80.f, 90.f, 100.f};
52     private static final float[] CUSTOM_INVALID_RS2 = {79.9f, 100.1f};
53 
54     private static final float CSD_VALUE_100PERC = 1.0f;
55 
56     private static final AudioAttributes ATTRIBUTES = new AudioAttributes.Builder()
57             .setUsage(USAGE_MEDIA)
58             .setContentType(CONTENT_TYPE_MUSIC)
59             .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM)
60             .build();
61 
62     private final AtomicInteger mDisplayedCsdWarningTimes = new AtomicInteger(0);
63 
64     private Context mContext;
65 
66     private final IVolumeController mVolumeController = new IVolumeController.Stub() {
67         @Override
68         public void displaySafeVolumeWarning(int flags) throws RemoteException {
69             // do nothing
70         }
71 
72         @Override
73         public void volumeChanged(int streamType, int flags) throws RemoteException {
74             // do nothing
75         }
76 
77         @Override
78         public void masterMuteChanged(int flags) throws RemoteException {
79             // do nothing
80         }
81 
82         @Override
83         public void setLayoutDirection(int layoutDirection) throws RemoteException {
84             // do nothing
85         }
86 
87         @Override
88         public void dismiss() throws RemoteException {
89             // do nothing
90         }
91 
92         @Override
93         public void setA11yMode(int mode) throws RemoteException {
94             // do nothing
95         }
96 
97         @Override
98         public void displayCsdWarning(int warning, int displayDurationMs) throws RemoteException {
99             if (warning == CSD_WARNING_MOMENTARY_EXPOSURE) {
100                 mDisplayedCsdWarningTimes.incrementAndGet();
101             }
102         }
103     };
104 
105     @Override
setUp()106     protected void setUp() throws Exception {
107         super.setUp();
108         Log.e("SoundDoseHelperTest", "Calling setUp");
109         mContext = getContext();
110         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
111                 Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED,
112                 Manifest.permission.STATUS_BAR_SERVICE);
113     }
114 
115     @Override
tearDown()116     protected void tearDown() throws Exception {
117         super.tearDown();
118         Log.e("SoundDoseHelperTest", "Calling tearDownUp");
119         getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
120     }
121 
testGetSetRs2Value()122     public void testGetSetRs2Value() throws Exception {
123         final AudioManager am = new AudioManager(mContext);
124         if (!platformSupportsSoundDose("testGetSetRs2Value", am)) {
125             return;
126         }
127 
128         float prevRS2Value = am.getRs2Value();
129 
130         for (float rs2Value : CUSTOM_INVALID_RS2) {
131             am.setRs2Value(rs2Value);
132             Thread.sleep(TEST_TIMING_TOLERANCE_MS);  // waiting for RS2 to propagate
133             assertEquals(DEFAULT_RS2_VALUE, am.getRs2Value());
134         }
135 
136         for (float rs2Value : CUSTOM_VALID_RS2) {
137             am.setRs2Value(rs2Value);
138             Thread.sleep(TEST_TIMING_TOLERANCE_MS);  // waiting for RS2 to propagate
139             assertEquals(rs2Value, am.getRs2Value());
140         }
141 
142         // Restore the RS2 value
143         am.setRs2Value(prevRS2Value);
144     }
145 
testGetSetCsd()146     public void testGetSetCsd() throws Exception {
147         final AudioManager am = new AudioManager(mContext);
148         if (!platformSupportsSoundDose("testGetSetCsd", am)) {
149             return;
150         }
151 
152         am.setCsd(CSD_VALUE_100PERC);
153         Thread.sleep(TEST_TIMING_TOLERANCE_MS);  // waiting for CSD to propagate
154         assertEquals(CSD_VALUE_100PERC, am.getCsd());
155     }
156 
testFrameworkMomentaryExposure()157     public void testFrameworkMomentaryExposure() throws Exception {
158         final AudioManager am = new AudioManager(mContext);
159         if (!platformSupportsSoundDose("testFrameworkMomentaryExposure", am)) {
160             return;
161         }
162         if (hasAudioSilentProperty()) {
163             Log.w(TAG, "Device has ro.audio.silent set, skipping testFrameworkMomentaryExposure");
164             return;
165         }
166 
167         am.forceComputeCsdOnAllDevices(/* computeCsdOnAllDevices= */true);
168         am.forceUseFrameworkMel(/* useFrameworkMel= */true);
169         am.setRs2Value(MIN_RS2_VALUE);  // lower the RS2 as much as possible
170 
171         IVolumeController sysUiVolumeController = null;
172         int prevVolume = -1;
173         try {
174             sysUiVolumeController = am.getVolumeController();
175             prevVolume = am.getStreamVolume(STREAM_MUSIC);
176             am.setVolumeController(mVolumeController);
177 
178             playLoudSound(am);
179 
180             Thread.sleep(TEST_MAX_TIME_EXPOSURE_WARNING_MS);
181             assertTrue("Exposure warning should have been triggered once!",
182                     mDisplayedCsdWarningTimes.get() > 0);
183         } finally {
184             if (prevVolume != -1) {
185                 // restore the previous volume
186                 am.setStreamVolume(STREAM_MUSIC, prevVolume, /* flags= */0);
187             }
188             if (sysUiVolumeController != null) {
189                 // restore SysUI volume controller
190                 am.setVolumeController(sysUiVolumeController);
191             }
192             am.setRs2Value(DEFAULT_RS2_VALUE);  // restore RS2 to default
193         }
194     }
195 
playLoudSound(AudioManager am)196     private void playLoudSound(AudioManager am) throws Exception {
197         int maxVolume = am.getStreamMaxVolume(STREAM_MUSIC);
198         am.setStreamVolume(STREAM_MUSIC, maxVolume, /* flags= */0);
199 
200         final Object loadLock = new Object();
201         final SoundPool soundpool = new SoundPool.Builder()
202                 .setAudioAttributes(ATTRIBUTES)
203                 .setMaxStreams(1)
204                 .build();
205         // load a sound and play it once load completion is reported
206         soundpool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
207             assertEquals("Load completion error", 0 /*success expected*/, status);
208             synchronized (loadLock) {
209                 loadLock.notify();
210             }
211         });
212         final int loadId = soundpool.load(mContext, R.raw.sine1320hz5sec, 1/*priority*/);
213         synchronized (loadLock) {
214             loadLock.wait(TEST_TIMEOUT_SOUNDPOOL_LOAD_MS);
215         }
216 
217         int res = soundpool.play(loadId, 1.0f /*leftVolume*/, 1.0f /*rightVolume*/, 1 /*priority*/,
218                 0 /*loop*/, 1.0f/*rate*/);
219         assertTrue("Error playing sound through SoundPool", res > 0);
220     }
221 
platformSupportsSoundDose(String testName, AudioManager am)222     private boolean platformSupportsSoundDose(String testName, AudioManager am) {
223         if (!mContext.getPackageManager()
224                 .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
225             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
226                     + "audio output HAL, skipping test " + testName);
227             return false;
228         }
229 
230         if (!am.isCsdEnabled()) {
231             Log.w(TAG, "Device does not have the sound dose feature enabled, skipping test "
232                     + testName);
233             return false;
234         }
235 
236         return true;
237     }
238 }
239