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