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