1 /*
2  * Copyright (C) 2022 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.server.audio;
18 
19 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES;
20 import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN;
21 import static android.media.AudioManager.STREAM_MUSIC;
22 
23 import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME;
24 import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME;
25 import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME;
26 import static com.android.server.audio.AudioService.SAFE_MEDIA_VOLUME_MSG_START;
27 
28 import static java.lang.Math.floor;
29 
30 import android.annotation.NonNull;
31 import android.app.AlarmManager;
32 import android.app.PendingIntent;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.media.AudioAttributes;
36 import android.media.AudioDeviceAttributes;
37 import android.media.AudioManager;
38 import android.media.AudioSystem;
39 import android.media.ISoundDose;
40 import android.media.ISoundDoseCallback;
41 import android.media.SoundDoseRecord;
42 import android.os.Binder;
43 import android.os.Message;
44 import android.os.RemoteException;
45 import android.os.SystemClock;
46 import android.os.SystemProperties;
47 import android.os.UserHandle;
48 import android.provider.Settings;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.util.MathUtils;
52 import android.util.SparseIntArray;
53 
54 import com.android.internal.R;
55 import com.android.internal.annotations.GuardedBy;
56 import com.android.server.audio.AudioService.AudioHandler;
57 import com.android.server.audio.AudioService.ISafeHearingVolumeController;
58 import com.android.server.audio.AudioServiceEvents.SoundDoseEvent;
59 import com.android.server.utils.EventLogger;
60 
61 import java.io.PrintWriter;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collection;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.concurrent.atomic.AtomicBoolean;
69 import java.util.concurrent.atomic.AtomicReference;
70 import java.util.stream.Collectors;
71 
72 /**
73  * Safe media volume management.
74  * MUSIC stream volume level is limited when headphones are connected according to safety
75  * regulation. When the user attempts to raise the volume above the limit, a warning is
76  * displayed and the user has to acknowledge before the volume is actually changed.
77  * The volume index corresponding to the limit is stored in config_safe_media_volume_index
78  * property. Platforms with a different limit must set this property accordingly in their
79  * overlay.
80  */
81 public class SoundDoseHelper {
82     private static final String TAG = "AS.SoundDoseHelper";
83 
84     /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
85             "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
86 
87     /**
88      * Property to force the index based safe volume warnings. Note that usually when the
89      * CSD warnings are active the safe volume warnings are deactivated. In combination with
90      * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE} both approaches can be active
91      * at the same time.
92      */
93     private static final String SYSTEM_PROPERTY_SAFEMEDIA_FORCE = "audio.safemedia.force";
94     /** Property for bypassing the index based safe volume approach. */
95     private static final String SYSTEM_PROPERTY_SAFEMEDIA_BYPASS = "audio.safemedia.bypass";
96     /**
97      * Property to force the CSD warnings. Note that usually when the CSD warnings are active the
98      * safe volume warnings are deactivated. In combination with
99      * {@link SoundDoseHelper#SYSTEM_PROPERTY_SAFEMEDIA_FORCE} both approaches can be active
100      * at the same time.
101      */
102     private static final String SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE = "audio.safemedia.csd.force";
103 
104     // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
105     // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
106     // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
107     // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
108     // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
109     // (when user opts out).
110     private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
111     private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
112     private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
113     private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
114 
115     /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA = SAFE_MEDIA_VOLUME_MSG_START + 1;
116     /*package*/ static final int MSG_CONFIGURE_SAFE_MEDIA_FORCED = SAFE_MEDIA_VOLUME_MSG_START + 2;
117     /*package*/ static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 3;
118     /*package*/ static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 4;
119     /*package*/ static final int MSG_PERSIST_CSD_VALUES = SAFE_MEDIA_VOLUME_MSG_START + 5;
120     /*package*/ static final int MSG_CSD_UPDATE_ATTENUATION = SAFE_MEDIA_VOLUME_MSG_START + 6;
121 
122     /*package*/ static final int MSG_LOWER_VOLUME_TO_RS1 = SAFE_MEDIA_VOLUME_MSG_START + 7;
123 
124 
125     private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
126 
127     private static final int MOMENTARY_EXPOSURE_TIMEOUT_MS = (20 * 3600 * 1000); // 20 hours
128 
129     private static final int MOMENTARY_EXPOSURE_TIMEOUT_UNINITIALIZED = -1;
130 
131     // 30s after boot completed
132     private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;
133 
134     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
135     private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
136 
137     // timeouts for the CSD warnings, -1 means no timeout (dialog must be ack'd by user)
138     private static final int CSD_WARNING_TIMEOUT_MS_DOSE_1X = 7000;
139     private static final int CSD_WARNING_TIMEOUT_MS_DOSE_5X = 5000;
140     private static final int CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START = -1;
141     private static final int CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE = 5000;
142 
143     private static final String PERSIST_CSD_RECORD_FIELD_SEPARATOR = ",";
144     private static final String PERSIST_CSD_RECORD_SEPARATOR_CHAR = "|";
145     private static final String PERSIST_CSD_RECORD_SEPARATOR = "\\|";
146 
147     private static final long GLOBAL_TIME_OFFSET_UNINITIALIZED = -1;
148 
149     private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
150 
151     // see {@link #recordToPersistedString(SoundDoseRecord)}
152     // this is computed conservatively to accommodate the legacy persisting of SoundDoseRecords in
153     // which we materialized more decimal values.
154     // TODO: adjust value after soaking in
155     private static final int MAX_RECORDS_STRING_LENGTH = 50;
156     private static final int MAX_SETTINGS_LENGTH = 32768;
157     private static final int MAX_NUMBER_OF_CACHED_RECORDS =
158             MAX_SETTINGS_LENGTH / MAX_RECORDS_STRING_LENGTH;
159 
160     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
161             "CSD updates");
162 
163     private int mMcc = 0;
164 
165     private final Object mSafeMediaVolumeStateLock = new Object();
166     private int mSafeMediaVolumeState;
167 
168     // Used when safe volume warning message display is requested by setStreamVolume(). In this
169     // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
170     // and used later when/if disableSafeMediaVolume() is called.
171     private StreamVolumeCommand mPendingVolumeCommand;
172 
173     // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
174     private int mSafeMediaVolumeIndex;
175     // mSafeMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
176     // property, divided by 100.0.
177     // For now using the same value for CSD supported devices
178     private float mSafeMediaVolumeDbfs;
179 
180     /**
181      * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
182      * Contains a safe volume index for a given device type.
183      * Indexes are used for headsets and is the music volume UI index
184      * corresponding to a gain of mSafeMediaVolumeDbfs (defaulting to -37dB) in audio
185      * flinger mixer.
186      * We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
187      * amplification when both effects are on with all band gains at maximum.
188      * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
189      * the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
190      */
191     private final SparseIntArray mSafeMediaVolumeDevices = new SparseIntArray();
192 
193     // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
194     // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
195     // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
196     private int mMusicActiveMs;
197     private long mLastMusicActiveTimeMs = 0;
198     private PendingIntent mMusicActiveIntent = null;
199     private final AlarmManager mAlarmManager;
200 
201     @NonNull private final AudioService mAudioService;
202     @NonNull private final SettingsAdapter mSettings;
203     @NonNull private final AudioHandler mAudioHandler;
204     @NonNull private final ISafeHearingVolumeController mVolumeController;
205 
206     private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
207 
208     private final AtomicBoolean mForceCsdProperty = new AtomicBoolean(false);
209 
210     private final Object mCsdAsAFeatureLock = new Object();
211 
212     @GuardedBy("mCsdAsAFeatureLock")
213     private boolean mIsCsdAsAFeatureAvailable = false;
214 
215     @GuardedBy("mCsdAsAFeatureLock")
216     private boolean mIsCsdAsAFeatureEnabled = false;
217 
218     private final ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
219             new ArrayList<>();
220 
221     private final Object mCsdStateLock = new Object();
222 
223     private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>();
224 
225     @GuardedBy("mCsdStateLock")
226     private float mCurrentCsd = 0.f;
227 
228     @GuardedBy("mCsdStateLock")
229     private long mLastMomentaryExposureTimeMs = MOMENTARY_EXPOSURE_TIMEOUT_UNINITIALIZED;
230 
231     // dose at which the next dose reached warning occurs
232     @GuardedBy("mCsdStateLock")
233     private float mNextCsdWarning = 1.0f;
234     @GuardedBy("mCsdStateLock")
235     private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
236 
237     // time in seconds reported by System.currentTimeInMillis used as an offset to convert between
238     // boot time and global time
239     @GuardedBy("mCsdStateLock")
240     private long mGlobalTimeOffsetInSecs = GLOBAL_TIME_OFFSET_UNINITIALIZED;
241 
242     private final Context mContext;
243 
244     private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
245         public void onMomentaryExposure(float currentMel, int deviceId) {
246             if (!mEnableCsd.get()) {
247                 Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback");
248                 return;
249             }
250 
251             Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
252                     + currentMel);
253             mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
254 
255             boolean postWarning = false;
256             synchronized (mCsdStateLock) {
257                 if (mLastMomentaryExposureTimeMs < 0
258                         || (System.currentTimeMillis() - mLastMomentaryExposureTimeMs)
259                         >= MOMENTARY_EXPOSURE_TIMEOUT_MS) {
260                     mLastMomentaryExposureTimeMs = System.currentTimeMillis();
261                     postWarning = true;
262                 }
263             }
264 
265             if (postWarning) {
266                 mVolumeController.postDisplayCsdWarning(
267                         AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE,
268                         getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE));
269             }
270         }
271 
272         public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
273             if (!mEnableCsd.get()) {
274                 Log.w(TAG, "onNewCsdValue: csd not supported, ignoring value");
275                 return;
276             }
277 
278             Log.i(TAG, "onNewCsdValue: " + currentCsd);
279             synchronized (mCsdStateLock) {
280                 if (mCurrentCsd < currentCsd) {
281                     // dose increase: going over next threshold?
282                     if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
283                         if (mNextCsdWarning == 5.0f) {
284                             // 500% dose repeat
285                             mVolumeController.postDisplayCsdWarning(
286                                     AudioManager.CSD_WARNING_DOSE_REPEATED_5X,
287                                     getTimeoutMsForWarning(
288                                             AudioManager.CSD_WARNING_DOSE_REPEATED_5X));
289                             // on the 5x dose warning, the volume reduction happens right away
290                             mAudioService.postLowerVolumeToRs1();
291                         } else {
292                             mVolumeController.postDisplayCsdWarning(
293                                     AudioManager.CSD_WARNING_DOSE_REACHED_1X,
294                                     getTimeoutMsForWarning(
295                                             AudioManager.CSD_WARNING_DOSE_REACHED_1X));
296                         }
297                         mNextCsdWarning += 1.0f;
298                     }
299                 } else {
300                     // dose decrease: dropping below previous threshold of warning?
301                     if ((currentCsd < mNextCsdWarning - 1.0f) && (
302                             mNextCsdWarning >= 2.0f)) {
303                         mNextCsdWarning -= 1.0f;
304                     }
305                 }
306                 mCurrentCsd = currentCsd;
307                 updateSoundDoseRecords_l(records, currentCsd);
308             }
309         }
310     };
311 
SoundDoseHelper(@onNull AudioService audioService, Context context, @NonNull AudioHandler audioHandler, @NonNull SettingsAdapter settings, @NonNull ISafeHearingVolumeController volumeController)312     SoundDoseHelper(@NonNull AudioService audioService, Context context,
313             @NonNull AudioHandler audioHandler,
314             @NonNull SettingsAdapter settings,
315             @NonNull ISafeHearingVolumeController volumeController) {
316         mAudioService = audioService;
317         mAudioHandler = audioHandler;
318         mSettings = settings;
319         mVolumeController = volumeController;
320 
321         mContext = context;
322 
323         initSafeVolumes();
324 
325         mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
326                 Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
327 
328         // The default safe volume index read here will be replaced by the actual value when
329         // the mcc is read by onConfigureSafeMedia()
330         // For now we use the same index for RS2 initial warning with CSD
331         mSafeMediaVolumeIndex = mContext.getResources().getInteger(
332                 R.integer.config_safe_media_volume_index) * 10;
333 
334         mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
335         // Csd will be initially disabled until the mcc is read in onConfigureSafeMedia()
336         initCsd();
337 
338         mAlarmManager = (AlarmManager) mContext.getSystemService(
339                 Context.ALARM_SERVICE);
340     }
341 
initSafeVolumes()342     void initSafeVolumes() {
343         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
344                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
345         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
346                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
347         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_USB_HEADSET,
348                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
349         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_HEADSET,
350                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
351         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
352                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
353         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
354                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
355         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
356                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
357     }
358 
getOutputRs2UpperBound()359     float getOutputRs2UpperBound() {
360         if (!mEnableCsd.get()) {
361             return 0.f;
362         }
363 
364         final ISoundDose soundDose = mSoundDose.get();
365         if (soundDose == null) {
366             Log.w(TAG, "Sound dose interface not initialized");
367             return 0.f;
368         }
369 
370         try {
371             return soundDose.getOutputRs2UpperBound();
372         } catch (RemoteException e) {
373             Log.e(TAG, "Exception while getting the RS2 exposure value", e);
374             return 0.f;
375         }
376     }
377 
setOutputRs2UpperBound(float rs2Value)378     void setOutputRs2UpperBound(float rs2Value) {
379         if (!mEnableCsd.get()) {
380             return;
381         }
382 
383         final ISoundDose soundDose = mSoundDose.get();
384         if (soundDose == null) {
385             Log.w(TAG, "Sound dose interface not initialized");
386             return;
387         }
388 
389         try {
390             soundDose.setOutputRs2UpperBound(rs2Value);
391         } catch (RemoteException e) {
392             Log.e(TAG, "Exception while setting the RS2 exposure value", e);
393         }
394     }
395 
updateCsdForTestApi()396     private boolean updateCsdForTestApi() {
397         if (mForceCsdProperty.get() != SystemProperties.getBoolean(
398                 SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false)) {
399             updateCsdEnabled("SystemPropertiesChangeCallback");
400         }
401 
402         return mEnableCsd.get();
403     }
404 
getCsd()405     float getCsd() {
406         if (!mEnableCsd.get()) {
407             // since this will only be called by a test api enable csd if system property is set
408             if (!updateCsdForTestApi()) {
409                 return -1.f;
410             }
411         }
412 
413         final ISoundDose soundDose = mSoundDose.get();
414         if (soundDose == null) {
415             Log.w(TAG, "Sound dose interface not initialized");
416             return -1.f;
417         }
418 
419         try {
420             return soundDose.getCsd();
421         } catch (RemoteException e) {
422             Log.e(TAG, "Exception while getting the CSD value", e);
423             return -1.f;
424         }
425     }
426 
setCsd(float csd)427     void setCsd(float csd) {
428         if (!mEnableCsd.get()) {
429             // since this will only be called by a test api enable csd if system property is set
430             if (!updateCsdForTestApi()) {
431                 return;
432             }
433         }
434 
435         SoundDoseRecord[] doseRecordsArray;
436         synchronized (mCsdStateLock) {
437             mCurrentCsd = csd;
438             mNextCsdWarning = (float) floor(csd + 1.0);
439 
440             mDoseRecords.clear();
441 
442             if (mCurrentCsd > 0.0f) {
443                 final SoundDoseRecord record = new SoundDoseRecord();
444                 record.timestamp = SystemClock.elapsedRealtime() / 1000;
445                 record.value = csd;
446                 mDoseRecords.add(record);
447             }
448             doseRecordsArray = mDoseRecords.toArray(new SoundDoseRecord[0]);
449         }
450 
451         final ISoundDose soundDose = mSoundDose.get();
452         if (soundDose == null) {
453             Log.w(TAG, "Sound dose interface not initialized");
454             return;
455         }
456 
457         try {
458             soundDose.resetCsd(csd, doseRecordsArray);
459         } catch (RemoteException e) {
460             Log.e(TAG, "Exception while setting the CSD value", e);
461         }
462     }
463 
resetCsdTimeouts()464     void resetCsdTimeouts() {
465         if (!mEnableCsd.get()) {
466             // since this will only be called by a test api enable csd if system property is set
467             if (!updateCsdForTestApi()) {
468                 return;
469             }
470         }
471 
472         synchronized (mCsdStateLock) {
473             mLastMomentaryExposureTimeMs = MOMENTARY_EXPOSURE_TIMEOUT_UNINITIALIZED;
474         }
475     }
476 
forceUseFrameworkMel(boolean useFrameworkMel)477     void forceUseFrameworkMel(boolean useFrameworkMel) {
478         if (!mEnableCsd.get()) {
479             // since this will only be called by a test api enable csd if system property is set
480             if (!updateCsdForTestApi()) {
481                 return;
482             }
483         }
484 
485         final ISoundDose soundDose = mSoundDose.get();
486         if (soundDose == null) {
487             Log.w(TAG, "Sound dose interface not initialized");
488             return;
489         }
490 
491         try {
492             soundDose.forceUseFrameworkMel(useFrameworkMel);
493         } catch (RemoteException e) {
494             Log.e(TAG, "Exception while forcing the internal MEL computation", e);
495         }
496     }
497 
forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices)498     void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
499         if (!mEnableCsd.get()) {
500             // since this will only be called by a test api enable csd if system property is set
501             if (!updateCsdForTestApi()) {
502                 return;
503             }
504         }
505 
506         final ISoundDose soundDose = mSoundDose.get();
507         if (soundDose == null) {
508             Log.w(TAG, "Sound dose interface not initialized");
509             return;
510         }
511 
512         try {
513             soundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
514         } catch (RemoteException e) {
515             Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
516         }
517     }
518 
isCsdEnabled()519     boolean isCsdEnabled() {
520         if (!mEnableCsd.get()) {
521             return false;
522         }
523 
524         final ISoundDose soundDose = mSoundDose.get();
525         if (soundDose == null) {
526             Log.w(TAG, "Sound dose interface not initialized");
527             return false;
528         }
529 
530         try {
531             return soundDose.isSoundDoseHalSupported();
532         } catch (RemoteException e) {
533             Log.e(TAG, "Exception while querying the csd enabled status", e);
534         }
535         return false;
536     }
537 
isCsdAsAFeatureAvailable()538     boolean isCsdAsAFeatureAvailable() {
539         synchronized (mCsdAsAFeatureLock) {
540             return mIsCsdAsAFeatureAvailable;
541         }
542     }
543 
isCsdAsAFeatureEnabled()544     boolean isCsdAsAFeatureEnabled() {
545         synchronized (mCsdAsAFeatureLock) {
546             return mIsCsdAsAFeatureEnabled;
547         }
548     }
549 
setCsdAsAFeatureEnabled(boolean csdAsAFeatureEnabled)550     void setCsdAsAFeatureEnabled(boolean csdAsAFeatureEnabled) {
551         boolean doUpdate;
552         synchronized (mCsdAsAFeatureLock) {
553             doUpdate = mIsCsdAsAFeatureEnabled != csdAsAFeatureEnabled && mIsCsdAsAFeatureAvailable;
554             mIsCsdAsAFeatureEnabled = csdAsAFeatureEnabled;
555             final long callingIdentity = Binder.clearCallingIdentity();
556             try {
557                 mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
558                         Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED,
559                         mIsCsdAsAFeatureEnabled ? 1 : 0, UserHandle.USER_CURRENT);
560             } finally {
561                 Binder.restoreCallingIdentity(callingIdentity);
562             }
563         }
564 
565         if (doUpdate) {
566             updateCsdEnabled("setCsdAsAFeatureEnabled");
567         }
568     }
569 
setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone)570     void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) {
571         if (!mEnableCsd.get()) {
572             return;
573         }
574 
575         final ISoundDose soundDose = mSoundDose.get();
576         if (soundDose == null) {
577             Log.w(TAG, "Sound dose interface not initialized");
578             return;
579         }
580 
581         try {
582             final ISoundDose.AudioDeviceCategory audioDeviceCategory =
583                     new ISoundDose.AudioDeviceCategory();
584             audioDeviceCategory.address = address;
585             audioDeviceCategory.internalAudioType = internalAudioType;
586             audioDeviceCategory.csdCompatible = isHeadphone;
587             soundDose.setAudioDeviceCategory(audioDeviceCategory);
588         } catch (RemoteException e) {
589             Log.e(TAG, "Exception while setting the audio device category", e);
590         }
591     }
592 
initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates)593     void initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates) {
594         for (final AdiDeviceState state : deviceStates) {
595             if (state.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN) {
596                 final ISoundDose.AudioDeviceCategory audioDeviceCategory =
597                         new ISoundDose.AudioDeviceCategory();
598                 audioDeviceCategory.address = state.getDeviceAddress();
599                 audioDeviceCategory.internalAudioType = state.getInternalDeviceType();
600                 audioDeviceCategory.csdCompatible =
601                         state.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES;
602                 mCachedAudioDeviceCategories.add(audioDeviceCategory);
603             }
604         }
605     }
606 
safeMediaVolumeIndex(int device)607     /*package*/ int safeMediaVolumeIndex(int device) {
608         final int vol = mSafeMediaVolumeDevices.get(device);
609         if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
610             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
611         }
612 
613         return vol;
614     }
615 
restoreMusicActiveMs()616     /*package*/ void restoreMusicActiveMs() {
617         synchronized (mSafeMediaVolumeStateLock) {
618             mMusicActiveMs = MathUtils.constrain(
619                     mSettings.getSecureIntForUser(mAudioService.getContentResolver(),
620                             Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0,
621                             UserHandle.USER_CURRENT),
622                     0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
623         }
624     }
625 
enforceSafeMediaVolumeIfActive(String caller)626     /*package*/ void enforceSafeMediaVolumeIfActive(String caller) {
627         synchronized (mSafeMediaVolumeStateLock) {
628             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
629                 enforceSafeMediaVolume(caller);
630             }
631         }
632     }
633 
enforceSafeMediaVolume(String caller)634     /*package*/ void enforceSafeMediaVolume(String caller) {
635         AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
636                 AudioSystem.STREAM_MUSIC);
637 
638         for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
639             int deviceType = mSafeMediaVolumeDevices.keyAt(i);
640             int index = streamState.getIndex(deviceType);
641             int safeIndex = safeMediaVolumeIndex(deviceType);
642             if (index > safeIndex) {
643                 streamState.setIndex(safeIndex, deviceType, caller,
644                         true /*hasModifyAudioSettings*/);
645                 mAudioHandler.sendMessageAtTime(
646                         mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, deviceType,
647                                 /*arg2=*/0, streamState), /*delay=*/0);
648             }
649         }
650     }
651 
652     /**
653      * Returns {@code true} if the safe media actions can be applied for the given stream type,
654      * volume index and device.
655      **/
checkSafeMediaVolume(int streamType, int index, int device)656     /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
657         boolean result;
658         synchronized (mSafeMediaVolumeStateLock) {
659             result = checkSafeMediaVolume_l(streamType, index, device);
660         }
661         return result;
662     }
663 
664     @GuardedBy("mSafeMediaVolumeStateLock")
checkSafeMediaVolume_l(int streamType, int index, int device)665     private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
666         return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
667                     && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
668                     && safeDevicesContains(device)
669                     && (index > safeMediaVolumeIndex(device));
670     }
671 
willDisplayWarningAfterCheckVolume(int streamType, int index, int device, int flags)672     /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
673             int flags) {
674         synchronized (mSafeMediaVolumeStateLock) {
675             if (checkSafeMediaVolume_l(streamType, index, device)) {
676                 mVolumeController.postDisplaySafeVolumeWarning(flags);
677                 mPendingVolumeCommand = new StreamVolumeCommand(
678                         streamType, index, flags, device);
679                 return true;
680             }
681         }
682         return false;
683     }
684 
disableSafeMediaVolume(String callingPackage)685     /*package*/ void disableSafeMediaVolume(String callingPackage) {
686         synchronized (mSafeMediaVolumeStateLock) {
687             final long identity = Binder.clearCallingIdentity();
688             setSafeMediaVolumeEnabled(false, callingPackage);
689             Binder.restoreCallingIdentity(identity);
690 
691             if (mPendingVolumeCommand != null) {
692                 mAudioService.onSetStreamVolume(mPendingVolumeCommand.mStreamType,
693                         mPendingVolumeCommand.mIndex,
694                         mPendingVolumeCommand.mFlags,
695                         mPendingVolumeCommand.mDevice,
696                         callingPackage, true /*hasModifyAudioSettings*/,
697                         true /*canChangeMute*/);
698                 mPendingVolumeCommand = null;
699             }
700         }
701     }
702 
scheduleMusicActiveCheck()703     /*package*/ void scheduleMusicActiveCheck() {
704         synchronized (mSafeMediaVolumeStateLock) {
705             cancelMusicActiveCheck();
706             mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
707                     REQUEST_CODE_CHECK_MUSIC_ACTIVE,
708                     new Intent(ACTION_CHECK_MUSIC_ACTIVE),
709                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
710             mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
711                     SystemClock.elapsedRealtime()
712                             + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
713         }
714     }
715 
onCheckMusicActive(String caller, boolean isStreamActive)716     /*package*/ void onCheckMusicActive(String caller, boolean isStreamActive) {
717         synchronized (mSafeMediaVolumeStateLock) {
718             if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
719                 int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
720                 if (safeDevicesContains(device) && isStreamActive) {
721                     scheduleMusicActiveCheck();
722                     int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
723                             device);
724                     if (index > safeMediaVolumeIndex(device)) {
725                         // Approximate cumulative active music time
726                         long curTimeMs = SystemClock.elapsedRealtime();
727                         if (mLastMusicActiveTimeMs != 0) {
728                             mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
729                         }
730                         mLastMusicActiveTimeMs = curTimeMs;
731                         Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
732                         if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
733                             setSafeMediaVolumeEnabled(true, caller);
734                             mMusicActiveMs = 0;
735                         }
736                         saveMusicActiveMs();
737                     }
738                 } else {
739                     cancelMusicActiveCheck();
740                     mLastMusicActiveTimeMs = 0;
741                 }
742             }
743         }
744     }
745 
configureSafeMedia(boolean forced, String caller)746     /*package*/ void configureSafeMedia(boolean forced, String caller) {
747         int msg = forced ? MSG_CONFIGURE_SAFE_MEDIA_FORCED : MSG_CONFIGURE_SAFE_MEDIA;
748         mAudioHandler.removeMessages(msg);
749 
750         long time = 0;
751         if (forced) {
752             time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
753                     "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
754         }
755 
756         mAudioHandler.sendMessageAtTime(
757                 mAudioHandler.obtainMessage(msg, /*arg1=*/0, /*arg2=*/0, caller),
758                 time);
759     }
760 
initSafeMediaVolumeIndex()761     /*package*/ void initSafeMediaVolumeIndex() {
762         for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
763             int deviceType = mSafeMediaVolumeDevices.keyAt(i);
764             if (mSafeMediaVolumeDevices.valueAt(i) == SAFE_MEDIA_VOLUME_UNINITIALIZED) {
765                 mSafeMediaVolumeDevices.put(deviceType, getSafeDeviceMediaVolumeIndex(deviceType));
766             }
767         }
768     }
769 
getSafeMediaVolumeIndex(int device)770     /*package*/ int getSafeMediaVolumeIndex(int device) {
771         if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
772                 && safeDevicesContains(device)) {
773             return safeMediaVolumeIndex(device);
774         } else {
775             return -1;
776         }
777     }
778 
raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device, int flags)779     /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
780             int flags) {
781         if (!checkSafeMediaVolume(streamType, index, device)) {
782             return false;
783         }
784 
785         mVolumeController.postDisplaySafeVolumeWarning(flags);
786         return true;
787     }
788 
safeDevicesContains(int device)789     /*package*/ boolean safeDevicesContains(int device) {
790         return mSafeMediaVolumeDevices.get(device, SAFE_MEDIA_VOLUME_UNINITIALIZED) >= 0;
791     }
792 
invalidatePendingVolumeCommand()793     /*package*/ void invalidatePendingVolumeCommand() {
794         synchronized (mSafeMediaVolumeStateLock) {
795             mPendingVolumeCommand = null;
796         }
797     }
798 
handleMessage(Message msg)799     /*package*/ void handleMessage(Message msg) {
800         switch (msg.what) {
801             case MSG_CONFIGURE_SAFE_MEDIA_FORCED:
802             case MSG_CONFIGURE_SAFE_MEDIA:
803                 onConfigureSafeMedia((msg.what == MSG_CONFIGURE_SAFE_MEDIA_FORCED),
804                         (String) msg.obj);
805                 break;
806             case MSG_PERSIST_SAFE_VOLUME_STATE:
807                 onPersistSafeVolumeState(msg.arg1);
808                 break;
809             case MSG_PERSIST_MUSIC_ACTIVE_MS:
810                 final int musicActiveMs = msg.arg1;
811                 mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
812                         Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
813                         UserHandle.USER_CURRENT);
814                 break;
815             case MSG_PERSIST_CSD_VALUES:
816                 onPersistSoundDoseRecords();
817                 break;
818             case MSG_CSD_UPDATE_ATTENUATION:
819                 final int device = msg.arg1;
820                 final boolean isAbsoluteVolume = (msg.arg2 == 1);
821                 final AudioService.VolumeStreamState streamState =
822                         (AudioService.VolumeStreamState) msg.obj;
823 
824                 updateDoseAttenuation(streamState.getIndex(device), device,
825                         streamState.getStreamType(), isAbsoluteVolume);
826                 break;
827             case MSG_LOWER_VOLUME_TO_RS1:
828                 onLowerVolumeToRs1();
829                 break;
830             default:
831                 Log.e(TAG, "Unexpected msg to handle: " + msg.what);
832                 break;
833         }
834     }
835 
dump(PrintWriter pw)836     /*package*/ void dump(PrintWriter pw) {
837         pw.print("  mEnableCsd="); pw.println(mEnableCsd.get());
838         if (mEnableCsd.get()) {
839             synchronized (mCsdStateLock) {
840                 pw.print("  mCurrentCsd="); pw.println(mCurrentCsd);
841             }
842         }
843         pw.print("  mSafeMediaVolumeState=");
844         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
845         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
846         for (int i = 0; i < mSafeMediaVolumeDevices.size(); ++i)  {
847             pw.print("  mSafeMediaVolumeIndex["); pw.print(mSafeMediaVolumeDevices.keyAt(i));
848             pw.print("]="); pw.println(mSafeMediaVolumeDevices.valueAt(i));
849         }
850         pw.print("  mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
851         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
852         pw.print("  mMcc="); pw.println(mMcc);
853         pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
854         pw.println();
855         mLogger.dump(pw);
856         pw.println();
857     }
858 
reset(boolean resetISoundDose)859     /*package*/void reset(boolean resetISoundDose) {
860         Log.d(TAG, "Reset the sound dose helper");
861 
862         if (resetISoundDose) {
863             mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback));
864         }
865 
866         synchronized (mCsdStateLock) {
867             try {
868                 final ISoundDose soundDose = mSoundDose.get();
869                 if (soundDose != null && soundDose.asBinder().isBinderAlive()) {
870                     if (mCurrentCsd != 0.f) {
871                         Log.d(TAG,
872                                 "Resetting the saved sound dose value " + mCurrentCsd);
873                         SoundDoseRecord[] records = mDoseRecords.toArray(
874                                 new SoundDoseRecord[0]);
875                         soundDose.resetCsd(mCurrentCsd, records);
876                     }
877                 }
878             } catch (RemoteException e) {
879                 // noop
880             }
881         }
882     }
883 
updateDoseAttenuation(int newIndex, int device, int streamType, boolean isAbsoluteVolume)884     private void updateDoseAttenuation(int newIndex, int device, int streamType,
885             boolean isAbsoluteVolume) {
886         if (!mEnableCsd.get()) {
887             return;
888         }
889 
890         final ISoundDose soundDose = mSoundDose.get();
891         if (soundDose == null) {
892             Log.w(TAG,  "Can not apply attenuation. ISoundDose itf is null.");
893             return;
894         }
895 
896         try {
897             if (!isAbsoluteVolume) {
898                 // remove any possible previous attenuation
899                 soundDose.updateAttenuation(/* attenuationDB= */0.f, device);
900 
901                 return;
902             }
903 
904             if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
905                     && safeDevicesContains(device)) {
906                 soundDose.updateAttenuation(
907                         -AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
908                                 (newIndex + 5) / 10,
909                                 device), device);
910             }
911         } catch (RemoteException e) {
912             Log.e(TAG, "Could not apply the attenuation for MEL calculation with volume index "
913                     + newIndex, e);
914         }
915     }
916 
initCsd()917     private void initCsd() {
918         ISoundDose soundDose = mSoundDose.get();
919         if (soundDose == null) {
920             Log.w(TAG, "ISoundDose instance is null.");
921             return;
922         }
923 
924         try {
925             soundDose.setCsdEnabled(mEnableCsd.get());
926         } catch (RemoteException e) {
927             Log.e(TAG, "Cannot disable CSD", e);
928         }
929 
930         if (!mEnableCsd.get()) {
931             return;
932         }
933 
934         Log.v(TAG, "Initializing sound dose");
935 
936         try {
937             if (!mCachedAudioDeviceCategories.isEmpty()) {
938                 soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray(
939                         new ISoundDose.AudioDeviceCategory[0]));
940                 mCachedAudioDeviceCategories.clear();
941             }
942         } catch (RemoteException e) {
943             Log.e(TAG, "Exception while initializing the cached audio device categories", e);
944         }
945 
946         synchronized (mCsdAsAFeatureLock) {
947             mIsCsdAsAFeatureEnabled = mSettings.getSecureIntForUser(
948                     mAudioService.getContentResolver(),
949                     Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, 0,
950                     UserHandle.USER_CURRENT) != 0;
951         }
952 
953         synchronized (mCsdStateLock) {
954             if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
955                 mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
956             }
957 
958             float prevCsd = mCurrentCsd;
959             // Restore persisted values
960             mCurrentCsd = parseGlobalSettingFloat(
961                     Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
962             if (mCurrentCsd != prevCsd) {
963                 mNextCsdWarning = parseGlobalSettingFloat(
964                         Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
965                 final List<SoundDoseRecord> records = persistedStringToRecordList(
966                         mSettings.getGlobalString(mAudioService.getContentResolver(),
967                                 Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS),
968                         mGlobalTimeOffsetInSecs);
969                 if (records != null) {
970                     mDoseRecords.addAll(records);
971                     sanitizeDoseRecords_l();
972                 }
973             }
974         }
975 
976         reset(/*resetISoundDose=*/false);
977     }
978 
onConfigureSafeMedia(boolean force, String caller)979     private void onConfigureSafeMedia(boolean force, String caller) {
980         updateCsdEnabled(caller);
981 
982         synchronized (mSafeMediaVolumeStateLock) {
983             int mcc = mContext.getResources().getConfiguration().mcc;
984             if ((mMcc != mcc) || ((mMcc == 0) && force)) {
985                 mSafeMediaVolumeIndex = mContext.getResources().getInteger(
986                         com.android.internal.R.integer.config_safe_media_volume_index) * 10;
987                 initSafeMediaVolumeIndex();
988 
989                 updateSafeMediaVolume_l(caller);
990 
991                 mMcc = mcc;
992             }
993         }
994     }
995 
996     @GuardedBy("mSafeMediaVolumeStateLock")
updateSafeMediaVolume_l(String caller)997     private void updateSafeMediaVolume_l(String caller) {
998         boolean safeMediaVolumeBypass =
999                 SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false)
1000                         || mEnableCsd.get();
1001         boolean safeMediaVolumeForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE,
1002                 false);
1003         // we are using the MCC overlaid legacy flag used for the safe volume enablement
1004         // to determine whether the MCC enforces any safe hearing standard.
1005         boolean mccEnforcedSafeMediaVolume = mContext.getResources().getBoolean(
1006                 com.android.internal.R.bool.config_safe_media_volume_enabled);
1007 
1008         boolean safeVolumeEnabled =
1009                 (mccEnforcedSafeMediaVolume || safeMediaVolumeForce) && !safeMediaVolumeBypass;
1010 
1011         // The persisted state is either "disabled" or "active": this is the state applied
1012         // next time we boot and cannot be "inactive"
1013         int persistedState;
1014         if (safeVolumeEnabled) {
1015             persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
1016             // The state can already be "inactive" here if the user has forced it before
1017             // the 30 seconds timeout for forced configuration. In this case we don't reset
1018             // it to "active".
1019             if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
1020                 if (mMusicActiveMs == 0) {
1021                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
1022                     enforceSafeMediaVolume(caller);
1023                 } else {
1024                     // We have existing playback time recorded, already confirmed.
1025                     mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
1026                     mLastMusicActiveTimeMs = 0;
1027                 }
1028             }
1029         } else {
1030             persistedState = SAFE_MEDIA_VOLUME_DISABLED;
1031             mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
1032         }
1033 
1034         mAudioHandler.sendMessageAtTime(
1035                 mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
1036                         persistedState, /*arg2=*/0,
1037                         /*obj=*/null), /*delay=*/0);
1038     }
1039 
updateCsdEnabled(String caller)1040     private void updateCsdEnabled(String caller) {
1041         mForceCsdProperty.set(SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
1042                 false));
1043         // we are using the MCC overlaid legacy flag used for the safe volume enablement
1044         // to determine whether the MCC enforces any safe hearing standard.
1045         boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
1046                 com.android.internal.R.bool.config_safe_media_volume_enabled);
1047         boolean csdEnable = mContext.getResources().getBoolean(
1048                 R.bool.config_safe_sound_dosage_enabled);
1049         boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || mForceCsdProperty.get();
1050 
1051         synchronized (mCsdAsAFeatureLock) {
1052             if (!mccEnforcedSafeMedia && csdEnable) {
1053                 mIsCsdAsAFeatureAvailable = true;
1054                 newEnabledCsd = mIsCsdAsAFeatureEnabled || mForceCsdProperty.get();
1055                 Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
1056                         + newEnabledCsd);
1057             } else {
1058                 mIsCsdAsAFeatureAvailable = false;
1059             }
1060         }
1061 
1062         if (mEnableCsd.compareAndSet(!newEnabledCsd, newEnabledCsd)) {
1063             Log.i(TAG, caller + ": enabled CSD " + newEnabledCsd);
1064             initCsd();
1065 
1066             synchronized (mSafeMediaVolumeStateLock) {
1067                 initSafeMediaVolumeIndex();
1068                 updateSafeMediaVolume_l(caller);
1069             }
1070         }
1071     }
1072 
getTimeoutMsForWarning(@udioManager.CsdWarning int csdWarning)1073     private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {
1074         switch (csdWarning) {
1075             case AudioManager.CSD_WARNING_DOSE_REACHED_1X:
1076                 return CSD_WARNING_TIMEOUT_MS_DOSE_1X;
1077             case AudioManager.CSD_WARNING_DOSE_REPEATED_5X:
1078                 return CSD_WARNING_TIMEOUT_MS_DOSE_5X;
1079             case AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE:
1080                 return CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE;
1081             case AudioManager.CSD_WARNING_ACCUMULATION_START:
1082                 return CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START;
1083         }
1084         Log.e(TAG, "Invalid CSD warning " + csdWarning, new Exception());
1085         return -1;
1086     }
1087 
1088     @GuardedBy("mSafeMediaVolumeStateLock")
setSafeMediaVolumeEnabled(boolean on, String caller)1089     private void setSafeMediaVolumeEnabled(boolean on, String caller) {
1090         if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && (mSafeMediaVolumeState
1091                 != SAFE_MEDIA_VOLUME_DISABLED)) {
1092             if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
1093                 mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
1094                 enforceSafeMediaVolume(caller);
1095             } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
1096                 mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
1097                 mMusicActiveMs = 1;  // nonzero = confirmed
1098                 mLastMusicActiveTimeMs = 0;
1099                 saveMusicActiveMs();
1100                 scheduleMusicActiveCheck();
1101             }
1102         }
1103     }
1104 
1105     @GuardedBy("mSafeMediaVolumeStateLock")
cancelMusicActiveCheck()1106     private void cancelMusicActiveCheck() {
1107         if (mMusicActiveIntent != null) {
1108             mAlarmManager.cancel(mMusicActiveIntent);
1109             mMusicActiveIntent = null;
1110         }
1111     }
1112 
1113     @GuardedBy("mSafeMediaVolumeStateLock")
saveMusicActiveMs()1114     private void saveMusicActiveMs() {
1115         mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
1116     }
1117 
getSafeDeviceMediaVolumeIndex(int deviceType)1118     private int getSafeDeviceMediaVolumeIndex(int deviceType) {
1119         if (!mEnableCsd.get()) {
1120             // legacy hearing safety only for wired and USB HS/HP
1121             if (deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
1122                     || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) {
1123                 // legacy hearing safety uses mSafeMediaVolumeIndex for wired HS/HP
1124                 // instead of computing it from the volume curves
1125                 return mSafeMediaVolumeIndex;
1126             }
1127 
1128             if (deviceType != AudioSystem.DEVICE_OUT_USB_HEADSET) {
1129                 return SAFE_MEDIA_VOLUME_UNINITIALIZED;
1130             }
1131         }
1132 
1133         // determine UI volume index corresponding to the wanted safe gain in dBFS
1134         int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
1135         int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
1136 
1137         mSafeMediaVolumeDbfs = mContext.getResources().getInteger(
1138                 com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
1139 
1140         while (Math.abs(max - min) > 1) {
1141             int index = (max + min) / 2;
1142             float gainDB = AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, index,
1143                     deviceType);
1144             if (Float.isNaN(gainDB)) {
1145                 //keep last min in case of read error
1146                 break;
1147             } else if (gainDB == mSafeMediaVolumeDbfs) {
1148                 min = index;
1149                 break;
1150             } else if (gainDB < mSafeMediaVolumeDbfs) {
1151                 min = index;
1152             } else {
1153                 max = index;
1154             }
1155         }
1156         return min * 10;
1157     }
1158 
onPersistSafeVolumeState(int state)1159     private void onPersistSafeVolumeState(int state) {
1160         mSettings.putGlobalInt(mAudioService.getContentResolver(),
1161                 Settings.Global.AUDIO_SAFE_VOLUME_STATE,
1162                 state);
1163     }
1164 
safeMediaVolumeStateToString(int state)1165     private static String safeMediaVolumeStateToString(int state) {
1166         switch(state) {
1167             case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
1168             case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
1169             case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
1170             case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
1171         }
1172         return null;
1173     }
1174 
1175     @GuardedBy("mCsdStateLock")
updateSoundDoseRecords_l(SoundDoseRecord[] newRecords, float currentCsd)1176     private void updateSoundDoseRecords_l(SoundDoseRecord[] newRecords, float currentCsd) {
1177         long totalDuration = 0;
1178         for (SoundDoseRecord record : newRecords) {
1179             Log.i(TAG, "  new record: " + record);
1180             totalDuration += record.duration;
1181 
1182             if (record.value < 0) {
1183                 // Negative value means the record timestamp exceeded the CSD rolling window size
1184                 // and needs to be removed
1185                 if (!mDoseRecords.removeIf(
1186                         r -> r.value == -record.value && r.timestamp == record.timestamp
1187                                 && r.averageMel == record.averageMel
1188                                 && r.duration == record.duration)) {
1189                     Log.w(TAG, "Could not find cached record to remove: " + record);
1190                 }
1191             } else if (record.value > 0) {
1192                 mDoseRecords.add(record);
1193             }
1194         }
1195 
1196         sanitizeDoseRecords_l();
1197 
1198         mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
1199                 /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
1200 
1201         mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
1202     }
1203 
1204     @GuardedBy("mCsdStateLock")
sanitizeDoseRecords_l()1205     private void sanitizeDoseRecords_l() {
1206         if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
1207             int nrToRemove = mDoseRecords.size() - MAX_NUMBER_OF_CACHED_RECORDS;
1208             Log.w(TAG,
1209                     "Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
1210             // Remove older elements to fit into persisted settings max length
1211             Iterator<SoundDoseRecord> recordIterator = mDoseRecords.iterator();
1212             while (recordIterator.hasNext() && nrToRemove > 0) {
1213                 recordIterator.next();
1214                 recordIterator.remove();
1215                 --nrToRemove;
1216             }
1217         }
1218     }
1219 
1220     @SuppressWarnings("GuardedBy")  // avoid limitation with intra-procedural analysis of lambdas
onPersistSoundDoseRecords()1221     private void onPersistSoundDoseRecords() {
1222         synchronized (mCsdStateLock) {
1223             if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
1224                 mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
1225             }
1226 
1227             mSettings.putGlobalString(mAudioService.getContentResolver(),
1228                     Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
1229                     Float.toString(mCurrentCsd));
1230             mSettings.putGlobalString(mAudioService.getContentResolver(),
1231                     Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING,
1232                     Float.toString(mNextCsdWarning));
1233             mSettings.putGlobalString(mAudioService.getContentResolver(),
1234                     Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
1235                     mDoseRecords.stream().map(
1236                             record -> SoundDoseHelper.recordToPersistedString(record,
1237                                     mGlobalTimeOffsetInSecs)).collect(
1238                             Collectors.joining(PERSIST_CSD_RECORD_SEPARATOR_CHAR)));
1239         }
1240     }
1241 
recordToPersistedString(SoundDoseRecord record, long globalTimeOffsetInSecs)1242     private static String recordToPersistedString(SoundDoseRecord record,
1243             long globalTimeOffsetInSecs) {
1244         return convertToGlobalTime(record.timestamp, globalTimeOffsetInSecs)
1245                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
1246                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.value)
1247                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + String.format("%.3f", record.averageMel);
1248     }
1249 
convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs)1250     private static long convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs) {
1251         return bootTimeInSecs + globalTimeOffsetInSecs;
1252     }
1253 
convertToBootTime(long globalTimeInSecs, long globalTimeOffsetInSecs)1254     private static long convertToBootTime(long globalTimeInSecs, long globalTimeOffsetInSecs) {
1255         return globalTimeInSecs - globalTimeOffsetInSecs;
1256     }
1257 
persistedStringToRecordList(String records, long globalTimeOffsetInSecs)1258     private static List<SoundDoseRecord> persistedStringToRecordList(String records,
1259             long globalTimeOffsetInSecs) {
1260         if (records == null || records.isEmpty()) {
1261             return null;
1262         }
1263         return Arrays.stream(TextUtils.split(records, PERSIST_CSD_RECORD_SEPARATOR)).map(
1264                 record -> SoundDoseHelper.persistedStringToRecord(record,
1265                         globalTimeOffsetInSecs)).filter(Objects::nonNull).collect(
1266                 Collectors.toList());
1267     }
1268 
persistedStringToRecord(String record, long globalTimeOffsetInSecs)1269     private static SoundDoseRecord persistedStringToRecord(String record,
1270             long globalTimeOffsetInSecs) {
1271         if (record == null || record.isEmpty()) {
1272             return null;
1273         }
1274         final String[] fields = TextUtils.split(record, PERSIST_CSD_RECORD_FIELD_SEPARATOR);
1275         if (fields.length != 4) {
1276             Log.w(TAG, "Expecting 4 fields for a SoundDoseRecord, parsed " + fields.length);
1277             return null;
1278         }
1279 
1280         final SoundDoseRecord sdRecord = new SoundDoseRecord();
1281         try {
1282             sdRecord.timestamp = convertToBootTime(Long.parseLong(fields[0]),
1283                     globalTimeOffsetInSecs);
1284             sdRecord.duration = Integer.parseInt(fields[1]);
1285             sdRecord.value = Float.parseFloat(fields[2]);
1286             sdRecord.averageMel = Float.parseFloat(fields[3]);
1287         } catch (NumberFormatException e) {
1288             Log.e(TAG, "Unable to parse persisted SoundDoseRecord: " + record, e);
1289             return null;
1290         }
1291 
1292         return sdRecord;
1293     }
1294 
parseGlobalSettingFloat(String audioSafeCsdCurrentValue, float defaultValue)1295     private float parseGlobalSettingFloat(String audioSafeCsdCurrentValue, float defaultValue) {
1296         String stringValue = mSettings.getGlobalString(mAudioService.getContentResolver(),
1297                 audioSafeCsdCurrentValue);
1298         if (stringValue == null || stringValue.isEmpty()) {
1299             Log.v(TAG, "No value stored in settings " + audioSafeCsdCurrentValue);
1300             return defaultValue;
1301         }
1302 
1303         float value;
1304         try {
1305             value = Float.parseFloat(stringValue);
1306         } catch (NumberFormatException e) {
1307             Log.e(TAG, "Error parsing float from settings " + audioSafeCsdCurrentValue, e);
1308             value = defaultValue;
1309         }
1310 
1311         return value;
1312     }
1313 
1314     /** Called when handling MSG_LOWER_VOLUME_TO_RS1 */
onLowerVolumeToRs1()1315     private void onLowerVolumeToRs1() {
1316         mLogger.enqueue(SoundDoseEvent.getLowerVolumeToRs1Event());
1317         final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt(
1318                 new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), true);
1319         final int nativeDeviceType;
1320         final AudioDeviceAttributes ada;
1321         if (!devices.isEmpty()) {
1322             ada = devices.get(0);
1323             nativeDeviceType = ada.getInternalType();
1324         } else {
1325             nativeDeviceType = AudioSystem.DEVICE_OUT_USB_HEADSET;
1326             ada = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_USB_HEADSET, "");
1327         }
1328         final int index = safeMediaVolumeIndex(nativeDeviceType);
1329         mAudioService.setStreamVolumeWithAttributionInt(STREAM_MUSIC, index / 10, /*flags*/ 0, ada,
1330                 mContext.getOpPackageName(), /*attributionTag=*/null,
1331                 true /*canChangeMuteAndUpdateController*/);
1332     }
1333 
1334     // StreamVolumeCommand contains the information needed to defer the process of
1335     // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
1336     private static class StreamVolumeCommand {
1337         public final int mStreamType;
1338         public final int mIndex;
1339         public final int mFlags;
1340         public final int mDevice;
1341 
StreamVolumeCommand(int streamType, int index, int flags, int device)1342         StreamVolumeCommand(int streamType, int index, int flags, int device) {
1343             mStreamType = streamType;
1344             mIndex = index;
1345             mFlags = flags;
1346             mDevice = device;
1347         }
1348 
1349         @Override
toString()1350         public String toString() {
1351             return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
1352                     .append(mIndex).append(",flags=").append(mFlags).append(",device=")
1353                     .append(mDevice).append('}').toString();
1354         }
1355     }
1356 }
1357