CSD: expose TestApi methods to test the feature

Exposing methods for getting/setting RS2 and CSD value. Also introducing
the possibility to force the internal MEL computation and to force CSD
for all output devices.
Removed unused mCurrentImeUid to unblock the presubmit.

Test: atest SoundDoseHelperTest
Bug: 248565894
Change-Id: Ic40d41597ca3a91b9e219192bbc01ea7f5afd084
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8c64e40..347f028 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1629,20 +1629,27 @@
 
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceComputeCsdOnAllDevices(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void forceUseFrameworkMel(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getCsd();
     method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
     method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
     method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public float getRs2Value();
     method public int getStreamMinVolumeInt(int);
     method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
     method public boolean hasRegisteredDynamicPolicy();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public boolean isCsdEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setCsd(float);
     method public void setRampingRingerEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setRs2Value(float);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
   }
 
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 24c5b41..b959bc0 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6443,6 +6443,20 @@
     }
 
     /**
+     * Returns the registered volume controller interface.
+     *
+     * @hide
+     */
+    @Nullable
+    public IVolumeController getVolumeController() {
+        try {
+            return getService().getVolumeController();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Notify audio manager about volume controller visibility changes.
      * Currently limited to SystemUI.
      *
@@ -6506,6 +6520,106 @@
 
     /**
      * @hide
+     * @return the RS2 value used for momentary exposure warnings
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public float getRs2Value() {
+        try {
+            return getService().getRs2Value();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the RS2 value used for momentary exposure warnings
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void setRs2Value(float rs2Value) {
+        try {
+            getService().setRs2Value(rs2Value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * @return the current computed sound dose value
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public float getCsd() {
+        try {
+            return getService().getCsd();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Sets the computed sound dose value to {@code csd}
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void setCsd(float csd) {
+        try {
+            getService().setCsd(csd);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Forces the computation of MEL values (used for CSD) on framework level. This will have the
+     * result of ignoring the MEL values computed on HAL level. Should only be used in testing
+     * since this can affect the certification of a device with EN50332-3 regulation.
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void forceUseFrameworkMel(boolean useFrameworkMel) {
+        try {
+            getService().forceUseFrameworkMel(useFrameworkMel);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Forces the computation of CSD on all output devices.
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+        try {
+            getService().forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Returns whether CSD is enabled on this device.
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public boolean isCsdEnabled() {
+        try {
+            return getService().isCsdEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Sound dose warning at every 100% of dose during integration window
      */
     public static final int CSD_WARNING_DOSE_REACHED_1X = 1;
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c06352c..5ba7891 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -260,6 +260,8 @@
 
     void setVolumeController(in IVolumeController controller);
 
+    @nullable IVolumeController getVolumeController();
+
     void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible);
 
     boolean isStreamAffectedByRingerMode(int streamType);
@@ -270,6 +272,27 @@
 
     void lowerVolumeToRs1(String callingPackage);
 
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    float getRs2Value();
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    oneway void setRs2Value(float rs2Value);
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    float getCsd();
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    oneway void setCsd(float csd);
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    oneway void forceUseFrameworkMel(boolean useFrameworkMel);
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    oneway void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices);
+
+    @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS")
+    boolean isCsdEnabled();
+
     int setHdmiSystemAudioSupported(boolean on);
 
     boolean isHdmiSystemAudioSupported();
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c641a85..b4598bf 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -646,6 +646,9 @@
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
+    <!-- Permission required for CTS test - SoundDoseHelperTest -->
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SYSTEM_SETTINGS" />
+
     <!-- Permission required for CTS test - CtsAmbientContextDetectionServiceDeviceTest -->
     <uses-permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" />
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c794b04..9a9b870 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -908,9 +908,6 @@
 
     private final SoundDoseHelper mSoundDoseHelper;
 
-    @GuardedBy("mSettingsLock")
-    private int mCurrentImeUid;
-
     private final Object mSupportedSystemUsagesLock = new Object();
     @GuardedBy("mSupportedSystemUsagesLock")
     private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -9939,6 +9936,55 @@
                 "com.android.server.audio", "AudioService");
     }
 
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public float getRs2Value() {
+        super.getRs2Value_enforcePermission();
+        return mSoundDoseHelper.getRs2Value();
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void setRs2Value(float rs2Value) {
+        super.setRs2Value_enforcePermission();
+        mSoundDoseHelper.setRs2Value(rs2Value);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public float getCsd() {
+        super.getCsd_enforcePermission();
+        return mSoundDoseHelper.getCsd();
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void setCsd(float csd) {
+        super.setCsd_enforcePermission();
+        mSoundDoseHelper.setCsd(csd);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void forceUseFrameworkMel(boolean useFrameworkMel) {
+        super.forceUseFrameworkMel_enforcePermission();
+        mSoundDoseHelper.forceUseFrameworkMel(useFrameworkMel);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+        super.forceComputeCsdOnAllDevices_enforcePermission();
+        mSoundDoseHelper.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+    }
+
+    @Override
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
+    public boolean isCsdEnabled() {
+        super.isCsdEnabled_enforcePermission();
+        return mSoundDoseHelper.isCsdEnabled();
+    }
+
     //==========================================================================================
     // Hdmi CEC:
     // - System audio mode:
@@ -10290,7 +10336,6 @@
                         + " FromRestrictions=" + mMicMuteFromRestrictions
                         + " FromApi=" + mMicMuteFromApi
                         + " from system=" + mMicMuteFromSystemCached);
-        pw.print("  mCurrentImeUid="); pw.println(mCurrentImeUid);
         dumpAccessibilityServiceUids(pw);
         dumpAssistantServicesUids(pw);
 
@@ -10425,6 +10470,15 @@
     }
 
     @Override
+    @Nullable
+    public IVolumeController getVolumeController() {
+        enforceVolumeController("get the volume controller");
+        if (DEBUG_VOL) Log.d(TAG, "Volume controller: " + mVolumeController);
+
+        return mVolumeController.getController();
+    }
+
+    @Override
     public void notifyVolumeControllerVisible(final IVolumeController controller, boolean visible) {
         enforceVolumeController("notify about volume controller visibility");
 
@@ -10469,6 +10523,10 @@
             mVisible = false;
         }
 
+        public IVolumeController getController() {
+            return mController;
+        }
+
         public void loadSettings(ContentResolver cr) {
             mLongPressTimeout = mSettings.getSecureIntForUser(cr,
                     Settings.Secure.LONG_PRESS_TIMEOUT, 500, UserHandle.USER_CURRENT);
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 919b850..bc61b37 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -53,6 +53,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -70,9 +71,6 @@
     /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
             "com.android.server.audio.action.CHECK_MUSIC_ACTIVE";
 
-    /** Flag to enable/disable the sound dose computation. */
-    private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
-
     // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
     // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
     // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
@@ -96,8 +94,6 @@
     private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
     private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
 
-    private static final float CUSTOM_RS2_VALUE = 90;
-
     // timeouts for the CSD warnings, -1 means no timeout (dialog must be ack'd by user)
     private static final int CSD_WARNING_TIMEOUT_MS_DOSE_1X = 7000;
     private static final int CSD_WARNING_TIMEOUT_MS_DOSE_5X = 5000;
@@ -233,6 +229,94 @@
         initCsd();
     }
 
+    float getRs2Value() {
+        if (!mEnableCsd) {
+            return 0.f;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            return mSoundDose.getOutputRs2();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while getting the RS2 exposure value", e);
+            return 0.f;
+        }
+    }
+
+    void setRs2Value(float rs2Value) {
+        if (!mEnableCsd) {
+            return;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            mSoundDose.setOutputRs2(rs2Value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while setting the RS2 exposure value", e);
+        }
+    }
+
+    float getCsd() {
+        if (!mEnableCsd) {
+            return -1.f;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            return mSoundDose.getCsd();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while getting the CSD value", e);
+            return -1.f;
+        }
+    }
+
+    void setCsd(float csd) {
+        if (!mEnableCsd) {
+            return;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            final SoundDoseRecord record = new SoundDoseRecord();
+            record.timestamp = System.currentTimeMillis();
+            record.value = csd;
+            final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
+            mSoundDose.resetCsd(csd, recordArray);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while setting the CSD value", e);
+        }
+    }
+
+    void forceUseFrameworkMel(boolean useFrameworkMel) {
+        if (!mEnableCsd) {
+            return;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            mSoundDose.forceUseFrameworkMel(useFrameworkMel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while forcing the internal MEL computation", e);
+        }
+    }
+
+    void forceComputeCsdOnAllDevices(boolean computeCsdOnAllDevices) {
+        if (!mEnableCsd) {
+            return;
+        }
+
+        Objects.requireNonNull(mSoundDose, "Sound dose interface not initialized");
+        try {
+            mSoundDose.forceComputeCsdOnAllDevices(computeCsdOnAllDevices);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception while forcing CSD computation on all devices", e);
+        }
+    }
+
+    boolean isCsdEnabled() {
+        return mEnableCsd;
+    }
+
     /*package*/ int safeMediaVolumeIndex(int device) {
         if (!mSafeMediaVolumeDevices.contains(device)) {
             return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
@@ -293,7 +377,7 @@
                     || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
                     || (!mSafeMediaVolumeDevices.contains(device))
                     || (index <= safeMediaVolumeIndex(device))
-                    || USE_CSD_FOR_SAFE_HEARING;
+                    || mEnableCsd;
     }
 
     /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
@@ -329,7 +413,7 @@
     /*package*/ void scheduleMusicActiveCheck() {
         synchronized (mSafeMediaVolumeStateLock) {
             cancelMusicActiveCheck();
-            if (!USE_CSD_FOR_SAFE_HEARING) {
+            if (!mEnableCsd) {
                 mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
                         REQUEST_CODE_CHECK_MUSIC_ACTIVE,
                         new Intent(ACTION_CHECK_MUSIC_ACTIVE),
@@ -460,14 +544,13 @@
 
     private void initCsd() {
         synchronized (mSafeMediaVolumeStateLock) {
-            if (USE_CSD_FOR_SAFE_HEARING) {
+            if (mEnableCsd) {
                 Log.v(TAG, "Initializing sound dose");
 
                 mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
                 mSoundDose = AudioSystem.getSoundDoseInterface(mSoundDoseCallback);
                 try {
                     if (mSoundDose != null && mSoundDose.asBinder().isBinderAlive()) {
-                        mSoundDose.setOutputRs2(CUSTOM_RS2_VALUE);
                         if (mCurrentCsd != 0.f) {
                             Log.d(TAG, "Resetting the saved sound dose value " + mCurrentCsd);
                             SoundDoseRecord[] records = mDoseRecords.toArray(
@@ -502,7 +585,7 @@
                 // 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 && !USE_CSD_FOR_SAFE_HEARING) {
+                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
                     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