Merge "Resolve flaky audio routing CUJ tests" into main
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 97129be..29e67bb 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -65,6 +65,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -120,6 +121,8 @@
     private boolean mAvailableRoutesUpdated;
     private final Object mLock = new Object();
     private final TelecomSystem.SyncRoot mTelecomLock;
+    private CountDownLatch mAudioOperationsCompleteLatch;
+    private CountDownLatch mAudioActiveCompleteLatch;
     private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -442,6 +445,12 @@
         } else {
             mCurrentRoute = DUMMY_ROUTE;
         }
+        // Audio ops will only ever be completed if there's a call placed and it gains
+        // ACTIVE/RINGING focus, hence why the initial value is 0.
+        mAudioOperationsCompleteLatch = new CountDownLatch(0);
+        // This latch will be count down when ACTIVE/RINGING focus is gained. This is determined
+        // when the routing goes active.
+        mAudioActiveCompleteLatch = new CountDownLatch(1);
         mIsActive = false;
         mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
                 supportMask, null, new HashSet<>());
@@ -1127,6 +1136,21 @@
             mIsPending = false;
             mPendingAudioRoute.clearPendingMessages();
             onCurrentRouteChanged();
+            if (mIsActive) {
+                // Reinitialize the audio ops complete latch since the routing went active. We
+                // should always expect operations to complete after this point.
+                if (mAudioOperationsCompleteLatch.getCount() == 0) {
+                    mAudioOperationsCompleteLatch = new CountDownLatch(1);
+                }
+                mAudioActiveCompleteLatch.countDown();
+            } else {
+                // Reinitialize the active routing latch when audio ops are complete so that it can
+                // once again be processed when a new call is placed/received.
+                if (mAudioActiveCompleteLatch.getCount() == 0) {
+                    mAudioActiveCompleteLatch = new CountDownLatch(1);
+                }
+                mAudioOperationsCompleteLatch.countDown();
+            }
             if (mFeatureFlags.telecomMetricsSupport()) {
                 mMetricsController.getAudioRouteStats().onRouteExit(mPendingAudioRoute, true);
             }
@@ -1659,4 +1683,12 @@
         sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE,
                 btAddressToExclude);
     }
+
+    public CountDownLatch getAudioOperationsCompleteLatch() {
+        return mAudioOperationsCompleteLatch;
+    }
+
+    public CountDownLatch getAudioActiveCompleteLatch() {
+        return mAudioActiveCompleteLatch;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index a8ddf86..39cd379 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -379,6 +379,7 @@
                 Analytics.THIRD_PARTY_PHONE);
     }
 
+    private static final long WAIT_FOR_AUDIO_UPDATE_TIMEOUT = 4000L;
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
      * calls are added to the map and removed when the calls move to the disconnected state.
@@ -513,6 +514,7 @@
 
     private final IncomingCallFilterGraphProvider mIncomingCallFilterGraphProvider;
     private final CallAudioWatchdog mCallAudioWatchDog;
+    private final CallAudioRouteAdapter mCallAudioRouteAdapter;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -691,12 +693,11 @@
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteAdapter callAudioRouteAdapter;
         // TODO: add another flag check when
         // bluetoothDeviceManager.getBluetoothHeadset().isScoManagedByAudio()
         // available and return true
         if (!featureFlags.useRefactoredAudioRouteSwitching()) {
-            callAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
+            mCallAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
                     context,
                     this,
                     bluetoothManager,
@@ -709,17 +710,17 @@
                     featureFlags
             );
         } else {
-            callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory,
-                    new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager,
-                    statusBarNotifier, featureFlags, metricsController);
+            mCallAudioRouteAdapter = new CallAudioRouteController(context, this,
+                    audioServiceFactory, new AudioRoute.Factory(), wiredHeadsetManager,
+                    mBluetoothRouteManager, statusBarNotifier, featureFlags, metricsController);
         }
-        callAudioRouteAdapter.initialize();
-        bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
-        bluetoothDeviceManager.setCallAudioRouteAdapter(callAudioRouteAdapter);
+        mCallAudioRouteAdapter.initialize();
+        bluetoothStateReceiver.setCallAudioRouteAdapter(mCallAudioRouteAdapter);
+        bluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteAdapter);
 
         CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
                 new CallAudioRoutePeripheralAdapter(
-                        callAudioRouteAdapter,
+                        mCallAudioRouteAdapter,
                         bluetoothManager,
                         wiredHeadsetManager,
                         mDockManager,
@@ -755,7 +756,7 @@
             mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
                     mTimeoutsAdapter, mLock);
         }
-        mCallAudioManager = new CallAudioManager(callAudioRouteAdapter,
+        mCallAudioManager = new CallAudioManager(mCallAudioRouteAdapter,
                 this, callAudioModeStateMachineFactory.create(systemStateHelper,
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE),
                 featureFlags, communicationDeviceTracker),
@@ -7121,4 +7122,23 @@
     public CallsManagerCallSequencingAdapter getCallSequencingAdapter() {
         return mCallSequencingAdapter;
     }
+
+    public void waitForAudioToUpdate(boolean expectActive) {
+        Log.i(this, "waitForAudioToUpdate");
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+            try {
+                CallAudioRouteController audioRouteController =
+                        (CallAudioRouteController) mCallAudioRouteAdapter;
+                if (expectActive) {
+                    audioRouteController.getAudioActiveCompleteLatch().await(
+                            WAIT_FOR_AUDIO_UPDATE_TIMEOUT, TimeUnit.MILLISECONDS);
+                } else {
+                    audioRouteController.getAudioOperationsCompleteLatch().await(
+                            WAIT_FOR_AUDIO_UPDATE_TIMEOUT, TimeUnit.MILLISECONDS);
+                }
+            } catch (InterruptedException e) {
+                Log.w(this, e.toString());
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 72cb297..ef8210d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -2952,6 +2952,10 @@
             }
         }
 
+        @Override
+        public void waitForAudioToUpdate(boolean expectActive) {
+            mCallsManager.waitForAudioToUpdate(expectActive);
+        }
         /**
          * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
          * calls for a given {@code packageName} and {@code userHandle}.
diff --git a/src/com/android/server/telecom/TelecomShellCommand.java b/src/com/android/server/telecom/TelecomShellCommand.java
index e840b2a..2e955a9 100644
--- a/src/com/android/server/telecom/TelecomShellCommand.java
+++ b/src/com/android/server/telecom/TelecomShellCommand.java
@@ -67,6 +67,10 @@
     private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
     private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
             "is-non-ui-in-call-service-bound";
+    private static final String COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION =
+            "wait-for-audio-ops-complete";
+    private static final String COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION =
+            "wait-for-audio-active";
 
     /**
      * Change the system dialer package name if a package name was specified,
@@ -192,6 +196,12 @@
                 case COMMAND_SET_METRICS_TEST_DISABLED:
                     mTelecomService.setMetricsTestMode(false);
                     break;
+                case COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION:
+                    mTelecomService.waitForAudioToUpdate(false);
+                    break;
+                case COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION:
+                    mTelecomService.waitForAudioToUpdate(true);
+                    break;
                 default:
                     return handleDefaultCommands(command);
             }