Merge "CSD: Implement new logic for enabling CSD" into udc-qpr-dev
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1235b78..99b0800 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9975,6 +9975,13 @@
         public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory";
 
         /**
+         * Stores a boolean that defines whether the CSD as a feature is enabled or not.
+         * @hide
+         */
+        public static final String AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED =
+                "audio_safe_csd_as_a_feature_enabled";
+
+        /**
          * Indicates whether notification display on the lock screen is enabled.
          * <p>
          * Type: int (0 for false, 1 for true)
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d86d53f..55b2517 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3013,14 +3013,15 @@
          on the headphone/microphone jack. When false use the older uevent framework. -->
     <bool name="config_useDevInputEventForAudioJack">false</bool>
 
-    <!-- Whether safe headphone volume is enabled or not (country specific). -->
+    <!-- Whether safe headphone hearing is enforced by any regulation (e.g.
+         EN50332-3, EN50332-2) or not (country specific). -->
     <bool name="config_safe_media_volume_enabled">true</bool>
 
-    <!-- Whether safe headphone sound dosage warning is enabled or not
-         (country specific). This value should only be overlaid to true
-         when a vendor supports offload and has the HAL sound dose
-         interfaces implemented. Otherwise, this can lead to a compliance
-         issue with the safe hearing standards EN50332-3 and IEC62368-1.
+    <!-- Whether safe headphone sound dosage warning is enabled or not.
+         This value should only be overlaid to true when a vendor supports
+         offload and has the HAL sound dose interfaces implemented.
+         Otherwise, this can lead to a compliance issue with the safe
+         hearing standards EN50332-3 and IEC62368-1.
     -->
     <bool name="config_safe_sound_dosage_enabled">false</bool>
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e8c9d0d..c3087bc 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6915,7 +6915,10 @@
 
     /**
      * @hide
-     * Returns whether CSD is enabled and supported by the HAL on this device.
+     * Returns whether CSD is enabled and supported by the current active audio module HAL.
+     * This method will return {@code false) for setups in which CSD as a feature is available
+     * (see {@link AudioManager#isCsdAsAFeatureAvailable()}) and not enabled (see
+     * {@link AudioManager#isCsdAsAFeatureEnabled()}).
      */
     @TestApi
     @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
@@ -6929,6 +6932,49 @@
 
     /**
      * @hide
+     * Returns whether CSD as a feature can be manipulated by a client. This method
+     * returns {@code true} in countries where there isn't a safe hearing regulation
+     * enforced.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean isCsdAsAFeatureAvailable() {
+        try {
+            return getService().isCsdAsAFeatureAvailable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns {@code true} if the client has enabled CSD. This function should only
+     * be called if {@link AudioManager#isCsdAsAFeatureAvailable()} returns {@code true}.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean isCsdAsAFeatureEnabled() {
+        try {
+            return getService().isCsdAsAFeatureEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Enables/disables the CSD feature. This function should only be called if
+     * {@link AudioManager#isCsdAsAFeatureAvailable()} returns {@code true}.
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setCsdAsAFeatureEnabled(boolean csdToggleValue) {
+        try {
+            getService().setCsdAsAFeatureEnabled(csdToggleValue);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Describes an audio device that has not been categorized with a specific
      * audio type.
      */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 180c7fd..9d62e37 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -323,6 +323,15 @@
     boolean isCsdEnabled();
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean isCsdAsAFeatureAvailable();
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean isCsdAsAFeatureEnabled();
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    oneway void setCsdAsAFeatureEnabled(boolean csdToggleValue);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
     oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType);
 
     @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9b8e473..91c72b5 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -703,7 +703,8 @@
                  Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
                  Settings.Secure.ASSIST_STRUCTURE_ENABLED,
                  Settings.Secure.ATTENTIVE_TIMEOUT,
-                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user
+                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // not controllable by user
+                 Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, // not controllable by user
                  Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
                  Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6a73c2b..ceb96ef 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -10691,6 +10691,27 @@
 
     @Override
     @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean isCsdAsAFeatureAvailable() {
+        super.isCsdAsAFeatureAvailable_enforcePermission();
+        return mSoundDoseHelper.isCsdAsAFeatureAvailable();
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public boolean isCsdAsAFeatureEnabled() {
+        super.isCsdAsAFeatureEnabled_enforcePermission();
+        return mSoundDoseHelper.isCsdAsAFeatureEnabled();
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
+    public void setCsdAsAFeatureEnabled(boolean csdToggleValue) {
+        super.setCsdAsAFeatureEnabled_enforcePermission();
+        mSoundDoseHelper.setCsdAsAFeatureEnabled(csdToggleValue);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED)
     public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle,
             @AudioDeviceCategory int btAudioDeviceCategory) {
         super.setBluetoothAudioDeviceCategory_enforcePermission();
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 851c5c3..5ebc1c0 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -37,13 +37,11 @@
 import android.media.ISoundDoseCallback;
 import android.media.SoundDoseRecord;
 import android.os.Binder;
-import android.os.HandlerExecutor;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -143,8 +141,6 @@
 
     private static final int SAFE_MEDIA_VOLUME_UNINITIALIZED = -1;
 
-    private static final String FEATURE_FLAG_ENABLE_CSD = "enable_csd";
-
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -193,7 +189,15 @@
 
     private final AtomicBoolean mEnableCsd = new AtomicBoolean(false);
 
-    private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
+    private final Object mCsdAsAFeatureLock = new Object();
+
+    @GuardedBy("mCsdAsAFeatureLock")
+    private boolean mIsCsdAsAFeatureAvailable = false;
+
+    @GuardedBy("mCsdAsAFeatureLock")
+    private boolean mIsCsdAsAFeatureEnabled = false;
+
+    private final ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories =
             new ArrayList<>();
 
     private final Object mCsdStateLock = new Object();
@@ -315,10 +319,6 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
-
-        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_MEDIA,
-                new HandlerExecutor(mAudioHandler),
-                p -> updateCsdEnabled("onPropertiesChanged"));
     }
 
     void initSafeVolumes() {
@@ -494,6 +494,38 @@
         return false;
     }
 
+    boolean isCsdAsAFeatureAvailable() {
+        synchronized (mCsdAsAFeatureLock) {
+            return mIsCsdAsAFeatureAvailable;
+        }
+    }
+
+    boolean isCsdAsAFeatureEnabled() {
+        synchronized (mCsdAsAFeatureLock) {
+            return mIsCsdAsAFeatureEnabled;
+        }
+    }
+
+    void setCsdAsAFeatureEnabled(boolean csdAsAFeatureEnabled) {
+        boolean doUpdate;
+        synchronized (mCsdAsAFeatureLock) {
+            doUpdate = mIsCsdAsAFeatureEnabled != csdAsAFeatureEnabled && mIsCsdAsAFeatureAvailable;
+            mIsCsdAsAFeatureEnabled = csdAsAFeatureEnabled;
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
+                        Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED,
+                        mIsCsdAsAFeatureEnabled ? 1 : 0, UserHandle.USER_CURRENT);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        if (doUpdate) {
+            updateCsdEnabled("setCsdAsAFeatureEnabled");
+        }
+    }
+
     void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) {
         if (!mEnableCsd.get()) {
             return;
@@ -864,6 +896,13 @@
             Log.e(TAG, "Exception while forcing the internal MEL computation", e);
         }
 
+        synchronized (mCsdAsAFeatureLock) {
+            mIsCsdAsAFeatureEnabled = mSettings.getSecureIntForUser(
+                    mAudioService.getContentResolver(),
+                    Settings.Secure.AUDIO_SAFE_CSD_AS_A_FEATURE_ENABLED, 0,
+                    UserHandle.USER_CURRENT) != 0;
+        }
+
         synchronized (mCsdStateLock) {
             if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
                 mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
@@ -908,18 +947,23 @@
 
     @GuardedBy("mSafeMediaVolumeStateLock")
     private void updateSafeMediaVolume_l(String caller) {
-        boolean safeMediaVolumeEnabled =
-                SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE, false)
-                        || (mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_safe_media_volume_enabled)
-                        && !mEnableCsd.get());
         boolean safeMediaVolumeBypass =
-                SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false);
+                SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_BYPASS, false)
+                        || mEnableCsd.get();
+        boolean safeMediaVolumeForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_FORCE,
+                false);
+        // we are using the MCC overlaid legacy flag used for the safe volume enablement
+        // to determine whether the MCC enforces any safe hearing standard.
+        boolean mccEnforcedSafeMediaVolume = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_safe_media_volume_enabled);
+
+        boolean safeVolumeEnabled =
+                (mccEnforcedSafeMediaVolume || safeMediaVolumeForce) && !safeMediaVolumeBypass;
 
         // The persisted state is either "disabled" or "active": this is the state applied
         // next time we boot and cannot be "inactive"
         int persistedState;
-        if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
+        if (safeVolumeEnabled) {
             persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
             // The state can already be "inactive" here if the user has forced it before
             // the 30 seconds timeout for forced configuration. In this case we don't reset
@@ -946,22 +990,28 @@
     }
 
     private void updateCsdEnabled(String caller) {
-        boolean newEnableCsd = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE,
-                false);
-        if (!newEnableCsd) {
-            final String featureFlagEnableCsdValue = DeviceConfig.getProperty(
-                    DeviceConfig.NAMESPACE_MEDIA,
-                    FEATURE_FLAG_ENABLE_CSD);
-            if (featureFlagEnableCsdValue != null) {
-                newEnableCsd = Boolean.parseBoolean(featureFlagEnableCsdValue);
+        boolean csdForce = SystemProperties.getBoolean(SYSTEM_PROPERTY_SAFEMEDIA_CSD_FORCE, false);
+        // we are using the MCC overlaid legacy flag used for the safe volume enablement
+        // to determine whether the MCC enforces any safe hearing standard.
+        boolean mccEnforcedSafeMedia = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_safe_media_volume_enabled);
+        boolean csdEnable = mContext.getResources().getBoolean(
+                R.bool.config_safe_sound_dosage_enabled);
+        boolean newEnabledCsd = (mccEnforcedSafeMedia && csdEnable) || csdForce;
+
+        synchronized (mCsdAsAFeatureLock) {
+            if (!mccEnforcedSafeMedia && csdEnable) {
+                mIsCsdAsAFeatureAvailable = true;
+                newEnabledCsd = mIsCsdAsAFeatureEnabled || csdForce;
+                Log.v(TAG, caller + ": CSD as a feature is not enforced and enabled: "
+                        + newEnabledCsd);
             } else {
-                newEnableCsd = mContext.getResources().getBoolean(
-                        R.bool.config_safe_sound_dosage_enabled);
+                mIsCsdAsAFeatureAvailable = false;
             }
         }
 
-        if (mEnableCsd.compareAndSet(!newEnableCsd, newEnableCsd)) {
-            Log.i(TAG, caller + ": enable CSD " + newEnableCsd);
+        if (mEnableCsd.compareAndSet(!newEnabledCsd, newEnabledCsd)) {
+            Log.i(TAG, caller + ": enabled CSD " + newEnabledCsd);
             initCsd();
 
             synchronized (mSafeMediaVolumeStateLock) {