AudioService: add test APIs for audio focus and ducking

Note: all new test methods end with "ForTest" so there is
 no ambiguity about the purpose of those methods.

Add test methods to protect focus-based tests from external
 audio focus uses (e.g. NotificationManager playing a
 notification on the DUT during a test).

Add test methods to read ducking-related timing values, and
 the list of ducked UIDs.
Add more debug messages (disabled by default).

Bug: 286495078
Test: atest CtsMediaAudioTestCases:android.media.audio.cts.AudioFocusTest#testDuckedUidsAfterMediaSpeech
Test: atest CtsMediaAudioTestCases:android.media.audio.cts.AudioFocusTest#testDuckedUidsAfterMediaMusic
Change-Id: Ie2fc243eecdc21f3b0718f6392e7345d3fb0ae07
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index e61c39f..b1c3b4ae 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1877,6 +1877,8 @@
 
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+    method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean enterAudioFocusFreezeForTest(@NonNull java.util.List<java.lang.Integer>);
+    method @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public boolean exitAudioFocusFreezeForTest();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceComputeCsdOnAllDevices(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void forceUseFrameworkMel(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
@@ -1884,6 +1886,9 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) 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 @NonNull @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public java.util.List<java.lang.Integer> getFocusDuckedUidsForTest();
+    method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusFadeOutDurationForTest();
+    method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFocusUnmuteDelayAfterFadeOutForTest();
     method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c3087bc..e2f4072 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4739,6 +4739,97 @@
 
     /**
      * @hide
+     * Test method to return the list of UIDs currently marked as ducked because of their
+     * audio focus status
+     * @return the list of UIDs, can be empty when no app is being ducked.
+     */
+    @TestApi
+    @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+    public @NonNull List<Integer> getFocusDuckedUidsForTest() {
+        try {
+            return getService().getFocusDuckedUidsForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Test method to return the duration of the fade out applied on the players of a focus loser
+     * @return the fade out duration in ms
+     */
+    @TestApi
+    @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+    public long getFocusFadeOutDurationForTest() {
+        try {
+            return getService().getFocusFadeOutDurationForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Test method to return the length of time after a fade-out before the focus loser is unmuted
+     * (and is faded back in).
+     * @return the time gap after a fade-out completion on focus loss, and fade-in start in ms.
+     */
+    @TestApi
+    @RequiresPermission("android.permission.QUERY_AUDIO_STATE")
+    public long getFocusUnmuteDelayAfterFadeOutForTest() {
+        try {
+            return getService().getFocusUnmuteDelayAfterFadeOutForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Test method to start preventing applications from requesting audio focus during a test,
+     * which could interfere with the functionality/behavior under test.
+     * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
+     * when the testing is done. If this is not the case (e.g. in case of a test crash),
+     * a death observer mechanism will ensure the system is not left in a bad state, but this should
+     * not be relied on when implementing tests.
+     * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
+     *     be those of the test runner and other players used in the test, or the "fake" UIDs used
+     *     for testing with {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)}.
+     * @return true if the focus freeze mode is successfully entered, false if there was an issue,
+     *     such as another freeze in place at the time of invocation.
+     *     A false result should result in a test failure as this would indicate the system is not
+     *     in a proper state with a predictable behavior for audio focus management.
+     */
+    @TestApi
+    @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    public boolean enterAudioFocusFreezeForTest(@NonNull List<Integer> exemptedUids) {
+        Objects.requireNonNull(exemptedUids);
+        try {
+            final int[] uids = exemptedUids.stream().mapToInt(Integer::intValue).toArray();
+            return getService().enterAudioFocusFreezeForTest(mICallBack, uids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Test method to end preventing applications from requesting audio focus during a test.
+     * @return true if the focus freeze mode is successfully exited, false if there was an issue,
+     *     such as the freeze already having ended, or not started.
+     */
+    @TestApi
+    @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    public boolean exitAudioFocusFreezeForTest() {
+        try {
+            return getService().exitAudioFocusFreezeForTest(mICallBack);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
      * Request or lock audio focus.
      * This method is to be used by system components that have registered an
      * {@link android.media.audiopolicy.AudioPolicy} to request audio focus, but also to "lock" it
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5cbb4e5..e45ef40 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -529,6 +529,23 @@
 
     long getFadeOutDurationOnFocusLossMillis(in AudioAttributes aa);
 
+    @EnforcePermission("QUERY_AUDIO_STATE")
+    /* Returns a List<Integer> */
+    @SuppressWarnings(value = {"untyped-collection"})
+    List getFocusDuckedUidsForTest();
+
+    @EnforcePermission("QUERY_AUDIO_STATE")
+    long getFocusFadeOutDurationForTest();
+
+    @EnforcePermission("QUERY_AUDIO_STATE")
+    long getFocusUnmuteDelayAfterFadeOutForTest();
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean enterAudioFocusFreezeForTest(IBinder cb, in int[] uids);
+
+    @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    boolean exitAudioFocusFreezeForTest(IBinder cb);
+
     void registerModeDispatcher(IAudioModeDispatcher dispatcher);
 
     oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bc0472..5f15995 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -43,6 +43,7 @@
 import static com.android.server.utils.EventLogger.Event.ALOGW;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -10005,6 +10006,14 @@
         return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
     }
 
+    /** see {@link AudioManager#getFocusDuckedUidsForTest()} */
+    @Override
+    @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+    public @NonNull List<Integer> getFocusDuckedUidsForTest() {
+        super.getFocusDuckedUidsForTest_enforcePermission();
+        return mPlaybackMonitor.getFocusDuckedUids();
+    }
+
     public void unregisterAudioFocusClient(String clientId) {
         new MediaMetrics.Item(mMetricsId + "focus")
                 .set(MediaMetrics.Property.CLIENT_NAME, clientId)
@@ -10021,6 +10030,68 @@
         return mMediaFocusControl.getFocusRampTimeMs(focusGain, attr);
     }
 
+    /**
+     * Test method to return the duration of the fade out applied on the players of a focus loser
+     * @see AudioManager#getFocusFadeOutDurationForTest()
+     * @return the fade out duration, in ms
+     */
+    @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+    public long getFocusFadeOutDurationForTest() {
+        super.getFocusFadeOutDurationForTest_enforcePermission();
+        return mMediaFocusControl.getFocusFadeOutDurationForTest();
+    }
+
+    /**
+     * Test method to return the length of time after a fade out before the focus loser is unmuted
+     * (and is faded back in).
+     * @see AudioManager#getFocusUnmuteDelayAfterFadeOutForTest()
+     * @return the time gap after a fade out completion on focus loss, and fade in start, in ms
+     */
+    @Override
+    @EnforcePermission("android.permission.QUERY_AUDIO_STATE")
+    public long getFocusUnmuteDelayAfterFadeOutForTest() {
+        super.getFocusUnmuteDelayAfterFadeOutForTest_enforcePermission();
+        return mMediaFocusControl.getFocusUnmuteDelayAfterFadeOutForTest();
+    }
+
+    /**
+     * Test method to start preventing applications from requesting audio focus during a test,
+     * which could interfere with the testing of the functionality/behavior under test.
+     * Calling this method needs to be paired with a call to {@link #exitAudioFocusFreezeForTest}
+     * when the testing is done. If this is not the case (e.g. in case of a test crash),
+     * a death observer mechanism will ensure the system is not left in a bad state, but this should
+     * not be relied on when implementing tests.
+     * @see AudioManager#enterAudioFocusFreezeForTest(List)
+     * @param cb IBinder to track the death of the client of this method
+     * @param exemptedUids a list of UIDs that are exempt from the freeze. This would for instance
+     *                     be those of the test runner and other players used in the test
+     * @return true if the focus freeze mode is successfully entered, false if there was an issue,
+     *     such as another freeze currently used.
+     */
+    @Override
+    @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    public boolean enterAudioFocusFreezeForTest(IBinder cb, int[] exemptedUids) {
+        super.enterAudioFocusFreezeForTest_enforcePermission();
+        Objects.requireNonNull(exemptedUids);
+        Objects.requireNonNull(cb);
+        return mMediaFocusControl.enterAudioFocusFreezeForTest(cb, exemptedUids);
+    }
+
+    /**
+     * Test method to end preventing applications from requesting audio focus during a test.
+     * @see AudioManager#exitAudioFocusFreezeForTest()
+     * @param cb IBinder identifying the client of this method
+     * @return true if the focus freeze mode is successfully exited, false if there was an issue,
+     *     such as the freeze already having ended, or not started.
+     */
+    @Override
+    @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+    public boolean exitAudioFocusFreezeForTest(IBinder cb) {
+        super.exitAudioFocusFreezeForTest_enforcePermission();
+        Objects.requireNonNull(cb);
+        return mMediaFocusControl.exitAudioFocusFreezeForTest(cb);
+    }
+
     /** only public for mocking/spying, do not call outside of AudioService */
     @VisibleForTesting
     public boolean hasAudioFocusUsers() {
@@ -10028,6 +10099,7 @@
     }
 
     /** see {@link AudioManager#getFadeOutDurationOnFocusLossMillis(AudioAttributes)} */
+    @Override
     public long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
         if (!enforceQueryAudioStateForTest("fade out duration")) {
             return 0;
diff --git a/services/core/java/com/android/server/audio/FocusRequester.java b/services/core/java/com/android/server/audio/FocusRequester.java
index 88a4b05..010d5f4 100644
--- a/services/core/java/com/android/server/audio/FocusRequester.java
+++ b/services/core/java/com/android/server/audio/FocusRequester.java
@@ -43,7 +43,7 @@
 public class FocusRequester {
 
     // on purpose not using this classe's name, as it will only be used from MediaFocusControl
-    private static final String TAG = "MediaFocusControl";
+    private static final String TAG = "FocusRequester";
     private static final boolean DEBUG = false;
 
     private AudioFocusDeathHandler mDeathHandler; // may be null
@@ -340,6 +340,9 @@
     @GuardedBy("MediaFocusControl.mAudioFocusLock")
     boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
     {
+        if (DEBUG) {
+            Log.i(TAG, "handleFocusLossFromGain for " + mClientId + " gain:" + focusGain);
+        }
         final int focusLoss = focusLossForGainRequest(focusGain);
         handleFocusLoss(focusLoss, frWinner, forceDuck);
         return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
@@ -378,6 +381,9 @@
     @GuardedBy("MediaFocusControl.mAudioFocusLock")
     void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
     {
+        if (DEBUG) {
+            Log.i(TAG, "handleFocusLoss for " + mClientId + " loss:" + focusLoss);
+        }
         try {
             if (focusLoss != mFocusLossReceived) {
                 mFocusLossReceived = focusLoss;
@@ -427,6 +433,9 @@
                             toAudioFocusInfo(), true /* wasDispatched */);
                     mFocusLossWasNotified = true;
                     fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);
+                } else if (DEBUG) {
+                    Log.i(TAG, "NOT dispatching " + focusChangeToString(mFocusLossReceived)
+                            + " to " + mClientId + " no IAudioFocusDispatcher");
                 }
             }
         } catch (android.os.RemoteException e) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index b218096..65f6c9b 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -45,6 +45,7 @@
 import java.io.PrintWriter;
 import java.text.DateFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -122,6 +123,23 @@
         dumpMultiAudioFocus(pw);
     }
 
+    /**
+     * Test method to return the duration of the fade out applied on the players of a focus loser
+     * @return the fade out duration in ms
+     */
+    public long getFocusFadeOutDurationForTest() {
+        return FadeOutManager.FADE_OUT_DURATION_MS;
+    }
+
+    /**
+     * Test method to return the length of time after a fade out before the focus loser is unmuted
+     * (and is faded back in).
+     * @return the time gap after a fade out completion on focus loss, and fade in start in ms
+     */
+    public long getFocusUnmuteDelayAfterFadeOutForTest() {
+        return FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS;
+    }
+
     //=================================================================
     // PlayerFocusEnforcer implementation
     @Override
@@ -304,17 +322,26 @@
     @GuardedBy("mAudioFocusLock")
     private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
                                                    boolean forceDuck) {
+        if (DEBUG) {
+            Log.i(TAG, "propagateFocusLossFromGain_syncAf gain:" + focusGain);
+        }
         final List<String> clientsToRemove = new LinkedList<String>();
         // going through the audio focus stack to signal new focus, traversing order doesn't
         // matter as all entries respond to the same external focus gain
         if (!mFocusStack.empty()) {
             for (FocusRequester focusLoser : mFocusStack) {
+                if (DEBUG) {
+                    Log.i(TAG, "propagateFocusLossFromGain_syncAf checking client:"
+                            + focusLoser.getClientId());
+                }
                 final boolean isDefinitiveLoss =
                         focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
                 if (isDefinitiveLoss) {
                     clientsToRemove.add(focusLoser.getClientId());
                 }
             }
+        } else if (DEBUG) {
+            Log.i(TAG, "propagateFocusLossFromGain_syncAf empty stack");
         }
 
         if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
@@ -370,6 +397,9 @@
     @GuardedBy("mAudioFocusLock")
     private void removeFocusStackEntry(String clientToRemove, boolean signal,
             boolean notifyFocusFollowers) {
+        if (DEBUG) {
+            Log.i(TAG, "removeFocusStackEntry client:" + clientToRemove);
+        }
         AudioFocusInfo abandonSource = null;
         // is the current top of the focus stack abandoning focus? (because of request, not death)
         if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
@@ -1000,6 +1030,24 @@
         }
 
         synchronized(mAudioFocusLock) {
+            // check whether a focus freeze is in place and filter
+            if (isFocusFrozenForTest()) {
+                int focusRequesterUid;
+                if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST)
+                        == AudioManager.AUDIOFOCUS_FLAG_TEST) {
+                    focusRequesterUid = testUid;
+                } else {
+                    focusRequesterUid = Binder.getCallingUid();
+                }
+                if (isFocusFrozenForTestForUid(focusRequesterUid)) {
+                    Log.i(TAG, "requestAudioFocus: focus frozen for test for uid:"
+                            + focusRequesterUid);
+                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+                }
+                Log.i(TAG, "requestAudioFocus: focus frozen for test but uid:" + focusRequesterUid
+                        + " is exempt");
+            }
+
             if (mFocusStack.size() > MAX_STACK_SIZE) {
                 Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
@@ -1191,6 +1239,110 @@
         return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
     }
 
+    /**
+     * Reference to the caller of {@link #enterAudioFocusFreezeForTest(IBinder, int[])}
+     * Will be null when there is no focus freeze for test
+     */
+    @GuardedBy("mAudioFocusLock")
+    @Nullable
+    private IBinder mFocusFreezerForTest = null;
+
+    /**
+     * The death handler for {@link #mFocusFreezerForTest}
+     * Will be null when there is no focus freeze for test
+     */
+    @GuardedBy("mAudioFocusLock")
+    @Nullable
+    private IBinder.DeathRecipient mFocusFreezerDeathHandler = null;
+
+    /**
+     *  Array of UIDs exempt from focus freeze when focus is frozen for test, null during normal
+     *  operations.
+     *  Will be null when there is no focus freeze for test
+     */
+    @GuardedBy("mAudioFocusLock")
+    @Nullable
+    private int[] mFocusFreezeExemptUids = null;
+
+    @GuardedBy("mAudioFocusLock")
+    private boolean isFocusFrozenForTest() {
+        return (mFocusFreezerForTest != null);
+    }
+
+    /**
+     * Checks if the given UID can request focus when a focus freeze is in place for a test.
+     * Focus can be requested if focus is not frozen or if it's frozen but the UID is exempt.
+     * @param uidToCheck
+     * @return true if that UID is barred from requesting focus, false if its focus request
+     *     can proceed being processed
+     */
+    @GuardedBy("mAudioFocusLock")
+    private boolean isFocusFrozenForTestForUid(int uidToCheck) {
+        if (isFocusFrozenForTest()) {
+            return false;
+        }
+        // check the list of exempts (array is not null because we're in a freeze for test
+        for (int uid : mFocusFreezeExemptUids) {
+            if (uid == uidToCheck) {
+                return false;
+            }
+        }
+        // uid was not found in the exempt list, its focus request is denied
+        return true;
+    }
+
+    protected boolean enterAudioFocusFreezeForTest(
+            @NonNull IBinder cb, @NonNull int[] exemptedUids) {
+        Log.i(TAG, "enterAudioFocusFreezeForTest UIDs exempt:" + Arrays.toString(exemptedUids));
+        synchronized (mAudioFocusLock) {
+            if (mFocusFreezerForTest != null) {
+                Log.e(TAG, "Error enterAudioFocusFreezeForTest: focus already frozen");
+                return false;
+            }
+            // new focus freeze, register death handler
+            try {
+                mFocusFreezerDeathHandler = new IBinder.DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Log.i(TAG, "Audio focus freezer died, exiting focus freeze for test");
+                        releaseFocusFreeze();
+                    }
+                };
+                cb.linkToDeath(mFocusFreezerDeathHandler, 0);
+                mFocusFreezerForTest = cb;
+                mFocusFreezeExemptUids = exemptedUids.clone();
+            } catch (RemoteException e) {
+                // client has already died!
+                mFocusFreezerForTest = null;
+                mFocusFreezeExemptUids = null;
+                return false;
+            }
+        }
+        return true;
+    }
+
+    protected boolean exitAudioFocusFreezeForTest(@NonNull IBinder cb) {
+        synchronized (mAudioFocusLock) {
+            if (mFocusFreezerForTest != cb) {
+                Log.e(TAG, "Error exitAudioFocusFreezeForTest: "
+                        + ((mFocusFreezerForTest == null)
+                        ? "call to exit while not frozen"
+                        : "call to exit not coming from freeze owner"));
+                return false;
+            }
+            mFocusFreezerForTest.unlinkToDeath(mFocusFreezerDeathHandler, 0);
+            releaseFocusFreeze();
+        }
+        return true;
+    }
+
+    private void releaseFocusFreeze() {
+        synchronized (mAudioFocusLock) {
+            mFocusFreezerDeathHandler = null;
+            mFocusFreezeExemptUids = null;
+            mFocusFreezerForTest = null;
+        }
+    }
 
     protected void unregisterAudioFocusClient(String clientId) {
         synchronized(mAudioFocusLock) {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 23a0782..54fa6fb 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -1208,6 +1208,17 @@
         }
     }
 
+    protected @NonNull List<Integer> getFocusDuckedUids() {
+        final ArrayList<Integer> duckedUids;
+        synchronized (mPlayerLock) {
+            duckedUids = new ArrayList(mDuckingManager.mDuckers.keySet());
+        }
+        if (DEBUG) {
+            Log.i(TAG, "current ducked UIDs: " + duckedUids);
+        }
+        return duckedUids;
+    }
+
     //=================================================================
     // For logging
     private static final class PlayerEvent extends EventLogger.Event {