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