Merge "Cache call events to ensure that cap exchange events are not missed" into main
diff --git a/Android.bp b/Android.bp
index 94654a6..0d1c81d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -84,9 +84,9 @@
         "tests/res",
     ],
     libs: [
-        "android.test.mock",
-        "android.test.base",
-        "android.test.runner",
+        "android.test.mock.stubs.system",
+        "android.test.base.stubs.system",
+        "android.test.runner.stubs.system",
     ],
 
     jni_libs: [
diff --git a/TEST_MAPPING b/TEST_MAPPING
index acab8ef..09ebfe2 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -58,5 +58,15 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsTelecomCujTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index 296b300..b060ed0 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -8,3 +8,11 @@
   description: "When getCurrentFocusCall times out, generate an anom. report"
   bug: "309541253"
 }
+
+# OWNER=tjstuart TARGET=25Q2
+flag {
+  name: "disconnect_self_managed_stuck_startup_calls"
+  namespace: "telecom"
+  description: "If a self-managed call is stuck in certain states, disconnect it"
+  bug: "360298368"
+}
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
index 331c328..2c53938 100644
--- a/flags/telecom_call_flags.aconfig
+++ b/flags/telecom_call_flags.aconfig
@@ -3,6 +3,13 @@
 
 # OWNER=tjstuart TARGET=24Q3
 flag {
+  name: "prevent_redundant_location_permission_grant_and_revoke"
+  namespace: "telecom"
+  description: "avoid redundant action of grant and revoke location permission for multiple emergency calls"
+  bug: "345386002"
+}
+
+flag {
   name: "transactional_cs_verifier"
   namespace: "telecom"
   description: "verify connection service callbacks via a transaction"
@@ -37,3 +44,14 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=breadley TARGET=24Q4
+flag {
+  name: "use_stream_voice_call_tones"
+  namespace: "telecom"
+  description: "Use STREAM_VOICE_CALL only for ToneGenerator"
+  bug: "363262590"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 3d69e13..dd04bcf 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -43,8 +43,8 @@
     <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"હાજરજવાબમાં ફેરફાર કરો"</string>
     <string name="respond_via_sms_setting_summary" msgid="8054571501085436868"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="6579353156073272157">"ઝડપી પ્રતિસાદ"</string>
-    <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર સંદેશ મોકલ્યો."</string>
-    <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>ને સંદેશ મોકલવામાં નિષ્ફળ રહ્યાં."</string>
+    <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર મેસેજ મોકલ્યો."</string>
+    <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર મેસેજ મોકલવામાં નિષ્ફળ રહ્યાં."</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"કૉલ કરવા માટેના એકાઉન્ટ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"ફક્ત કટોકટીના કૉલ્સને મંજૂરી છે."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"ફોન પરવાનગી વિના આ ઍપ્લિકેશન આઉટગોઇંગ કૉલ્સ કરી શકતી નથી."</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c607df5..0f6e41f 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -90,7 +90,7 @@
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Ако одговорите, ќе се прекине вашиот тековен видеоповик"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"Одговорете"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"Одбијте"</string>
-    <string name="cant_call_due_to_no_supported_service" msgid="6720817368116820027">"Не може да се оствари повик. Проверете ја мрежата на уредот."</string>
+    <string name="cant_call_due_to_no_supported_service" msgid="6720817368116820027">"Не може да се оствари повик. Проверете ја врската на уредот."</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"Не може да се воспостави повик поради вашиот повик на <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"Не може да се воспостави повик поради вашите повици на <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"Не може да се воспостави повик поради вашиот повик на друга апликација."</string>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 9845f1c..ba371f1 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -39,7 +39,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
@@ -2192,10 +2191,15 @@
                 isWorkCall = UserUtil.isManagedProfile(mContext, userHandle, mFlags);
             }
 
-            isCallRecordingToneSupported = (phoneAccount.hasCapabilities(
-                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) && phoneAccount.getExtras() != null
-                    && phoneAccount.getExtras().getBoolean(
-                    PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
+            if (!mFlags.telecomResolveHiddenDependencies()) {
+                isCallRecordingToneSupported = (phoneAccount.hasCapabilities(
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                        && phoneAccount.getExtras() != null
+                        && phoneAccount.getExtras().getBoolean(
+                        PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
+            } else {
+                isCallRecordingToneSupported = false;
+            }
             isSimCall = phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
         }
         mIsWorkCall = isWorkCall;
@@ -3891,7 +3895,6 @@
      * @param callerInfo The new caller information to set.
      */
     private void setCallerInfo(Uri handle, CallerInfo callerInfo) {
-        Trace.beginSection("setCallerInfo");
         if (callerInfo == null) {
             Log.i(this, "CallerInfo lookup returned null, skipping update");
             return;
@@ -3915,8 +3918,6 @@
                 l.onCallerInfoChanged(this);
             }
         }
-
-        Trace.endSection();
     }
 
     public CallerInfo getCallerInfo() {
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index 045671e..497d7e6 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -18,15 +18,22 @@
 
 import static com.android.server.telecom.LogUtils.Events.STATE_TIMEOUT;
 
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
 import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.stats.CallStateChangedAtomWriter;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Collections;
 import java.util.Map;
@@ -113,6 +120,7 @@
     private final TelecomSystem.SyncRoot mLock;
     private final Timeouts.Adapter mTimeoutAdapter;
     private final ClockProxy mClockProxy;
+    private final FeatureFlags mFeatureFlags;
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
     // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
     private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
@@ -140,6 +148,11 @@
             UUID.fromString("d57d8aab-d723-485e-a0dd-d1abb0f346c8");
     public static final String WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG =
             "Telecom CallAnomalyWatchdog caught and disconnected a stuck/zombie emergency call.";
+    public static final UUID WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_UUID =
+            UUID.fromString("3fbecd12-059d-4fd3-87b7-6c3079891c23");
+    public static final String WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_MSG =
+            "Telecom CallAnomalyWatchdog caught stuck VoIP call in a starting state";
+
 
     @VisibleForTesting
     public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
@@ -148,10 +161,12 @@
 
     public CallAnomalyWatchdog(ScheduledExecutorService executorService,
             TelecomSystem.SyncRoot lock,
+            FeatureFlags featureFlags,
             Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
             EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
         mScheduledExecutorService = executorService;
         mLock = lock;
+        mFeatureFlags = featureFlags;
         mTimeoutAdapter = timeoutAdapter;
         mClockProxy = clockProxy;
         mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
@@ -272,8 +287,13 @@
      */
     private void maybeTrackCall(Call call) {
         final WatchdogCallState currentState = mWatchdogCallStateMap.get(call);
+        boolean isCreateConnectionComplete = call.isCreateConnectionComplete();
+        if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
+            isCreateConnectionComplete =
+                    isCreateConnectionComplete || call.isTransactionalCall();
+        }
         final WatchdogCallState newState = new WatchdogCallState(call.getState(),
-                call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
+                isCreateConnectionComplete, mClockProxy.elapsedRealtime());
         if (Objects.equals(currentState, newState)) {
             // No state change; skip.
             return;
@@ -348,8 +368,13 @@
                 }
                 // Ensure that at timeout we are still in the original state when we posted the
                 // timeout.
+                boolean isCreateConnectionComplete = call.isCreateConnectionComplete();
+                if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
+                    isCreateConnectionComplete =
+                            isCreateConnectionComplete || call.isTransactionalCall();
+                }
                 final WatchdogCallState expiredState = new WatchdogCallState(call.getState(),
-                        call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
+                        isCreateConnectionComplete, mClockProxy.elapsedRealtime());
                 if (expiredState.equals(newState)
                         && getDurationInCurrentStateMillis(newState) > timeoutMillis) {
                     // The call has been in this transitory or intermediate state too long,
@@ -368,7 +393,7 @@
                                 WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
                     }
 
-                    if (isEnabledDisconnect) {
+                    if (isEnabledDisconnect || isInSelfManagedStuckStartingState(call)) {
                         call.setOverrideDisconnectCauseCode(
                                 new DisconnectCause(DisconnectCause.ERROR, "state_timeout"));
                         call.disconnect("State timeout");
@@ -387,6 +412,50 @@
         return cleanupRunnable;
     }
 
+    private boolean isInSelfManagedStuckStartingState(Call call) {
+        Context context = call.getContext();
+        if (!mFeatureFlags.disconnectSelfManagedStuckStartupCalls() || context == null) {
+            return false;
+        }
+        int currentStuckState = call.getState();
+        return call.isSelfManaged() &&
+                (currentStuckState == CallState.NEW ||
+                        currentStuckState == CallState.RINGING ||
+                        currentStuckState == CallState.DIALING ||
+                        currentStuckState == CallState.CONNECTING) &&
+                isVanillaIceCreamBuildOrHigher(context, call);
+    }
+
+    private boolean isVanillaIceCreamBuildOrHigher(Context context, Call call) {
+        // report the anomaly for metrics purposes
+        mAnomalyReporter.reportAnomaly(
+                WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_UUID,
+                WATCHDOG_DISCONNECTED_STUCK_VOIP_CALL_MSG);
+        // only disconnect calls running on V and when the flag is enabled!
+        PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+        PackageManager pm = context.getPackageManager();
+        if (pm == null ||
+                phoneAccountHandle == null ||
+                phoneAccountHandle.getComponentName() == null) {
+            return false;
+        }
+        String packageName = phoneAccountHandle.getComponentName().getPackageName();
+        Log.d(this, "pah=[%s], user=[%s]", phoneAccountHandle, call.getAssociatedUser());
+        ApplicationInfo applicationInfo;
+        try {
+            applicationInfo = pm.getApplicationInfoAsUser(
+                    packageName,
+                    0,
+                    call.getAssociatedUser());
+        } catch (Exception e) {
+            Log.e(this, e, "iVICBOH: pm.getApplicationInfoAsUser(...) exception");
+            return false;
+        }
+        int targetSdk = (applicationInfo == null) ? 0 : applicationInfo.targetSdkVersion;
+        Log.i(this, "iVICBOH: packageName=[%s], sdk=[%d]", packageName, targetSdk);
+        return targetSdk >= Build.VERSION_CODES.VANILLA_ICE_CREAM;
+    }
+
     /**
      * Returns whether the action to disconnect the call when the Transitory state and
      * Intermediate state time expires is enabled or disabled.
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index cd1d6a3..fb196f2 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -21,7 +21,6 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Trace;
 import android.telecom.Log;
 import android.telecom.Logging.Runnable;
 import android.telecom.Logging.Session;
@@ -461,40 +460,35 @@
         private boolean mHasFocus = false;
 
         private void tryStartRinging() {
-            Trace.traceBegin(Trace.TRACE_TAG_AUDIO, "CallAudioMode.tryStartRinging");
-            try {
-                if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
-                    Log.i(LOG_TAG,
-                        "RingingFocusState#tryStartRinging -- audio focus previously"
-                            + " acquired and ringtone already playing -- skipping.");
-                    return;
-                }
+            if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
+                Log.i(LOG_TAG,
+                    "RingingFocusState#tryStartRinging -- audio focus previously"
+                        + " acquired and ringtone already playing -- skipping.");
+                return;
+            }
 
-                if (mCallAudioManager.startRinging()) {
-                    if (mFeatureFlags.telecomResolveHiddenDependencies()) {
-                        mCurrentAudioFocusRequest = RING_AUDIO_FOCUS_REQUEST;
-                        Log.i(this, "tryStartRinging: AudioManager#requestAudioFocus(RING)");
-                        mAudioManager.requestAudioFocus(RING_AUDIO_FOCUS_REQUEST);
-                    } else {
-                        mAudioManager.requestAudioFocusForCall(
-                                AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-                    }
-                    // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
-                    // this trips up the audio system.
-                    if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
-                        Log.i(this, "enter: AudioManager#setMode(MODE_RINGTONE)");
-                        mAudioManager.setMode(AudioManager.MODE_RINGTONE);
-                        mLocalLog.log("Mode MODE_RINGTONE");
-                    }
-                    mCallAudioManager.setCallAudioRouteFocusState(
-                        CallAudioRouteStateMachine.RINGING_FOCUS);
-                    mHasFocus = true;
+            if (mCallAudioManager.startRinging()) {
+                if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                    mCurrentAudioFocusRequest = RING_AUDIO_FOCUS_REQUEST;
+                    Log.i(this, "tryStartRinging: AudioManager#requestAudioFocus(RING)");
+                    mAudioManager.requestAudioFocus(RING_AUDIO_FOCUS_REQUEST);
                 } else {
-                    Log.i(
-                        LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
+                    mAudioManager.requestAudioFocusForCall(
+                            AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                 }
-            } finally {
-                Trace.traceEnd(Trace.TRACE_TAG_AUDIO);
+                // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
+                // this trips up the audio system.
+                if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
+                    Log.i(this, "enter: AudioManager#setMode(MODE_RINGTONE)");
+                    mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+                    mLocalLog.log("Mode MODE_RINGTONE");
+                }
+                mCallAudioManager.setCallAudioRouteFocusState(
+                    CallAudioRouteStateMachine.RINGING_FOCUS);
+                mHasFocus = true;
+            } else {
+                Log.i(
+                    LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
             }
         }
 
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 9c593f4..abee2a8 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -533,14 +533,6 @@
                 mIsScoAudioConnected);
         mIsActive = active;
         mPendingAudioRoute.evaluatePendingState();
-        postTimeoutMessage();
-    }
-
-    private void postTimeoutMessage() {
-        // reset timeout handler
-        mHandler.removeMessages(PENDING_ROUTE_TIMEOUT);
-        mHandler.postDelayed(() -> mHandler.sendMessage(
-                Message.obtain(mHandler, PENDING_ROUTE_TIMEOUT)), TIMEOUT_LIMIT);
     }
 
     private void handleWiredHeadsetConnected() {
@@ -657,9 +649,6 @@
                 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_CONNECTED,
                         bluetoothDevice.getAddress()), null);
             }
-        } else {
-            // ignore, not triggered by telecom
-            Log.i(this, "handleBtAudioActive: ignoring handling bt audio active.");
         }
     }
 
@@ -679,9 +668,6 @@
                 mPendingAudioRoute.onMessageReceived(new Pair<>(BT_AUDIO_DISCONNECTED,
                         bluetoothDevice.getAddress()), null);
             }
-        } else {
-            // ignore, not triggered by telecom
-            Log.i(this, "handleBtAudioInactive: ignoring handling bt audio inactive.");
         }
     }
 
@@ -1156,7 +1142,8 @@
                     : mSpeakerDockRoute;
             // Ensure that we default to speaker route if we're in a video call, but disregard it if
             // a wired headset is plugged in.
-            if (skipEarpiece && defaultRoute.getType() == AudioRoute.TYPE_EARPIECE) {
+            if (skipEarpiece && defaultRoute != null
+                    && defaultRoute.getType() == AudioRoute.TYPE_EARPIECE) {
                 Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
                         + "speaker route for video call.");
                 defaultRoute = mSpeakerDockRoute;
@@ -1227,7 +1214,8 @@
 
     public AudioRoute getBaseRoute(boolean includeBluetooth, String btAddressToExclude) {
         AudioRoute destRoute = getPreferredAudioRouteFromStrategy();
-        if (destRoute == null || (destRoute.getBluetoothAddress() != null && !includeBluetooth)) {
+        if (destRoute == null || (destRoute.getBluetoothAddress() != null && (!includeBluetooth
+                || destRoute.getBluetoothAddress().equals(btAddressToExclude)))) {
             destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
         }
         if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 8e1f754..c77b9ff 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -14,7 +14,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Looper;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.DefaultDialerManager;
@@ -95,14 +94,12 @@
         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
 
-        Trace.beginSection("processNewCallCallIntent");
         if (isUnknownCall) {
             processUnknownCallIntent(mCallsManager, intent);
         } else {
             processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
                     mDefaultDialerCache, mFeatureFlags);
         }
-        Trace.endSection();
     }
 
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 600f847..a7ad3b8 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -75,7 +75,6 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.SystemVibrator;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.BlockedNumberContract;
@@ -444,6 +443,7 @@
     private final InCallController mInCallController;
     private final CallDiagnosticServiceController mCallDiagnosticServiceController;
     private final CallAudioManager mCallAudioManager;
+    /** @deprecated not used any more */
     private final CallRecordingTonePlayer mCallRecordingTonePlayer;
     private RespondViaSmsManager mRespondViaSmsManager;
     private final Ringer mRinger;
@@ -680,7 +680,7 @@
                                         audioManager.generateAudioSessionId()));
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
                 callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory,
-                () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0);
+                () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0, featureFlags);
 
         SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context, featureFlags);
@@ -696,8 +696,13 @@
                 new Ringer.VibrationEffectProxy(), mInCallController,
                 mContext.getSystemService(NotificationManager.class),
                 accessibilityManagerAdapter, featureFlags);
-        mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
-                mTimeoutsAdapter, mLock);
+        if (featureFlags.telecomResolveHiddenDependencies()) {
+            // This is now deprecated
+            mCallRecordingTonePlayer = null;
+        } else {
+            mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
+                    mTimeoutsAdapter, mLock);
+        }
         mCallAudioManager = new CallAudioManager(callAudioRouteAdapter,
                 this, callAudioModeStateMachineFactory.create(systemStateHelper,
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE),
@@ -742,7 +747,9 @@
         mListeners.add(mCallEndpointController);
         mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
-        mListeners.add(mCallRecordingTonePlayer);
+        if (!featureFlags.telecomResolveHiddenDependencies()) {
+            mListeners.add(mCallRecordingTonePlayer);
+        }
         mListeners.add(missedCallNotifier);
         mListeners.add(mDisconnectedCallNotifier);
         mListeners.add(mHeadsetMediaButton);
@@ -4621,7 +4628,6 @@
             Log.i(this, "addCall(%s) is already added");
             return;
         }
-        Trace.beginSection("addCall");
         Log.i(this, "addCall(%s)", call);
         call.addListener(this);
         mCalls.add(call);
@@ -4638,20 +4644,12 @@
         updateExternalCallCanPullSupport();
         // onCallAdded for calls which immediately take the foreground (like the first call).
         for (CallsManagerListener listener : mListeners) {
-            if (LogUtils.SYSTRACE_DEBUG) {
-                Trace.beginSection(listener.getClass().toString() + " addCall");
-            }
             listener.onCallAdded(call);
-            if (LogUtils.SYSTRACE_DEBUG) {
-                Trace.endSection();
-            }
         }
-        Trace.endSection();
     }
 
     @VisibleForTesting
     public void removeCall(Call call) {
-        Trace.beginSection("removeCall");
         Log.v(this, "removeCall(%s)", call);
 
         if (call.isTransactionalCall() && call.getTransactionServiceWrapper() != null) {
@@ -4678,16 +4676,9 @@
             updateCanAddCall();
             updateHasActiveRttCall();
             for (CallsManagerListener listener : mListeners) {
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
-                }
                 listener.onCallRemoved(call);
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.endSection();
-                }
             }
         }
-        Trace.endSection();
     }
 
     private void updateHasActiveRttCall() {
@@ -4750,13 +4741,8 @@
                 call.getAnalytics().setMissedReason(call.getMissedReason());
 
                 maybeShowErrorDialogOnDisconnect(call);
-
-                Trace.beginSection("onCallStateChanged");
-
                 maybeHandleHandover(call, newState);
                 notifyCallStateChanged(call, oldState, newState);
-
-                Trace.endSection();
             } else {
                 Log.i(this, "failed in setting the state to new state");
             }
@@ -4769,14 +4755,7 @@
             updateCanAddCall();
             updateHasActiveRttCall();
             for (CallsManagerListener listener : mListeners) {
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.beginSection(listener.getClass().toString() +
-                            " onCallStateChanged");
-                }
                 listener.onCallStateChanged(call, oldState, newState);
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.endSection();
-                }
             }
         }
     }
@@ -4901,13 +4880,7 @@
         if (newCanAddCall != mCanAddCall) {
             mCanAddCall = newCanAddCall;
             for (CallsManagerListener listener : mListeners) {
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.beginSection(listener.getClass().toString() + " updateCanAddCall");
-                }
                 listener.onCanAddCallChanged(mCanAddCall);
-                if (LogUtils.SYSTRACE_DEBUG) {
-                    Trace.endSection();
-                }
             }
         }
     }
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index 5ab0e99..c0e38ca 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -24,6 +24,7 @@
 import android.telecom.PhoneAccountHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.FeatureFlags;
 
 /**
  * Helps with emergency calls by:
@@ -51,19 +52,25 @@
     private long mLastEmergencyCallTimestampMillis;
     private long mLastOutgoingEmergencyCallTimestampMillis;
 
+    private final FeatureFlags mFeatureFlags;
+
     @VisibleForTesting
     public EmergencyCallHelper(
             Context context,
             DefaultDialerCache defaultDialerCache,
-            Timeouts.Adapter timeoutsAdapter) {
+            Timeouts.Adapter timeoutsAdapter,
+            FeatureFlags featureFlags) {
         mContext = context;
         mDefaultDialerCache = defaultDialerCache;
         mTimeoutsAdapter = timeoutsAdapter;
+        mFeatureFlags = featureFlags;
     }
 
     @VisibleForTesting
     public void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
-        if (shouldGrantTemporaryLocationPermission(call)) {
+        if (shouldGrantTemporaryLocationPermission(call) && (
+                !mFeatureFlags.preventRedundantLocationPermissionGrantAndRevoke()
+                || !wasGrantedTemporaryLocationPermission())) {
             grantLocationPermission(userHandle);
         }
         if (call != null && call.isEmergencyCall()) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 6164638..5a6f0ea 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -45,7 +45,6 @@
 import android.os.Looper;
 import android.os.PackageTagsList;
 import android.os.RemoteException;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.CallAudioState;
@@ -1302,6 +1301,8 @@
     private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>();
     private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>();
 
+    private java.lang.Runnable mCallRemovedRunnable;
+
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
             SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
             Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
@@ -1517,7 +1518,11 @@
             /** Let's add a 2 second delay before we send unbind to the services to hopefully
              *  give them enough time to process all the pending messages.
              */
-            mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
+            if (mCallRemovedRunnable != null
+                    && mFeatureFlags.preventRedundantLocationPermissionGrantAndRevoke()) {
+                mHandler.removeCallbacks(mCallRemovedRunnable);
+            }
+            mCallRemovedRunnable = new Runnable("ICC.oCR", mLock) {
                 @Override
                 public void loggedRun() {
                     // Check again to make sure there are no active calls for the associated user.
@@ -1531,8 +1536,10 @@
                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
                     }
                 }
-            }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
-                    mContext.getContentResolver()));
+            }.prepare();
+            mHandler.postDelayed(mCallRemovedRunnable,
+                    mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
+                            mContext.getContentResolver()));
         }
         call.removeListener(mCallListener);
         mCallIdMapper.removeCall(call);
@@ -2573,7 +2580,6 @@
             Log.e(this, e, "Failed to set the in-call adapter.");
             mAnomalyReporter.reportAnomaly(SET_IN_CALL_ADAPTER_ERROR_UUID,
                     SET_IN_CALL_ADAPTER_ERROR_MSG);
-            Trace.endSection();
             return false;
         }
 
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index a5942f0..b7edeb5 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -30,6 +30,7 @@
 import android.telecom.Logging.Session;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -54,15 +55,18 @@
         private final ToneGeneratorFactory mToneGeneratorFactory;
         private final MediaPlayerFactory mMediaPlayerFactory;
         private final AudioManagerAdapter mAudioManagerAdapter;
+        private final FeatureFlags mFeatureFlags;
 
         public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
                 TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory,
-                MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter) {
+                MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter,
+                FeatureFlags flags) {
             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
             mToneGeneratorFactory = toneGeneratorFactory;
             mMediaPlayerFactory = mediaPlayerFactory;
             mAudioManagerAdapter = audioManagerAdapter;
+            mFeatureFlags = flags;
         }
 
         public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -72,7 +76,7 @@
         public InCallTonePlayer createPlayer(Call call, int tone) {
             return new InCallTonePlayer(call, tone, mCallAudioManager,
                     mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
-                    mMediaPlayerFactory, mAudioManagerAdapter);
+                    mMediaPlayerFactory, mAudioManagerAdapter, mFeatureFlags);
         }
     }
 
@@ -216,6 +220,7 @@
     private final ToneGeneratorFactory mToneGenerator;
     private final MediaPlayerFactory mMediaPlayerFactory;
     private final AudioManagerAdapter mAudioManagerAdapter;
+    private final FeatureFlags mFeatureFlags;
 
     /**
      * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from
@@ -236,7 +241,8 @@
             TelecomSystem.SyncRoot lock,
             ToneGeneratorFactory toneGeneratorFactory,
             MediaPlayerFactory mediaPlayerFactor,
-            AudioManagerAdapter audioManagerAdapter) {
+            AudioManagerAdapter audioManagerAdapter,
+            FeatureFlags flags) {
         mCall = call;
         mState = STATE_OFF;
         mToneId = toneId;
@@ -246,6 +252,7 @@
         mToneGenerator = toneGeneratorFactory;
         mMediaPlayerFactory = mediaPlayerFactor;
         mAudioManagerAdapter = audioManagerAdapter;
+        mFeatureFlags = flags;
     }
 
     /** {@inheritDoc} */
@@ -364,18 +371,8 @@
                     throw new IllegalStateException("Bad toneId: " + mToneId);
             }
 
-            int stream = AudioManager.STREAM_VOICE_CALL;
-            if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
-                stream = AudioManager.STREAM_BLUETOOTH_SCO;
-            }
+            int stream = getStreamType(toneType);
             if (toneType != ToneGenerator.TONE_UNKNOWN) {
-                if (stream == AudioManager.STREAM_BLUETOOTH_SCO) {
-                    // Override audio stream for BT le device and hearing aid device
-                    if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn()
-                            || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) {
-                        stream = AudioManager.STREAM_VOICE_CALL;
-                    }
-                }
                 playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
             } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
                 playMediaTone(stream, mediaResourceId);
@@ -387,6 +384,31 @@
     }
 
     /**
+     * @param toneType The ToneGenerator tone type
+     * @return The ToneGenerator stream type
+     */
+    private int getStreamType(int toneType) {
+        if (mFeatureFlags.useStreamVoiceCallTones()) {
+            return AudioManager.STREAM_VOICE_CALL;
+        }
+
+        int stream = AudioManager.STREAM_VOICE_CALL;
+        if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
+            stream = AudioManager.STREAM_BLUETOOTH_SCO;
+        }
+        if (toneType != ToneGenerator.TONE_UNKNOWN) {
+            if (stream == AudioManager.STREAM_BLUETOOTH_SCO) {
+                // Override audio stream for BT le device and hearing aid device
+                if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn()
+                        || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) {
+                    stream = AudioManager.STREAM_VOICE_CALL;
+                }
+            }
+        }
+        return stream;
+    }
+
+    /**
      * Play a tone generated by the {@link ToneGenerator}.
      * @param stream The stream on which the tone will be played.
      * @param toneVolume The volume of the tone.
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index c24ac97..fce3f1a 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -25,7 +25,6 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.telecom.GatewayInfo;
 import android.telecom.Log;
@@ -126,7 +125,6 @@
         public void onReceive(Context context, Intent intent) {
             try {
                 Log.startSession("NOCBIR.oR");
-                Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
                 synchronized (mLock) {
                     Log.v(this, "onReceive: %s", intent);
 
@@ -194,7 +192,6 @@
                                     VideoProfile.STATE_AUDIO_ONLY));
                 }
             } finally {
-                Trace.endSection();
                 Log.endSession();
             }
         }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index d7dcf38..1cbe846 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -285,7 +285,7 @@
                             mContactsAsyncHelper, mLock);
 
             EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
-                    defaultDialerCache, timeoutsAdapter);
+                    defaultDialerCache, timeoutsAdapter, mFeatureFlags);
 
             InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
                 @Override
@@ -375,7 +375,8 @@
 
             CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
                     Executors.newSingleThreadScheduledExecutor(),
-                    mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
+                    mLock, mFeatureFlags, timeoutsAdapter, clockProxy,
+                    emergencyCallDiagnosticLogger);
 
             TransactionManager transactionManager = TransactionManager.getInstance();
 
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index b4c3d8d..cd52889 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -23,6 +23,8 @@
 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED;
 import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED;
 import static com.android.server.telecom.CallAudioRouteAdapter.PENDING_ROUTE_FAILED;
+import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
+import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
 
@@ -153,8 +155,19 @@
                     CallAudioRouteController audioRouteController =
                             (CallAudioRouteController) mCallAudioRouteAdapter;
                     audioRouteController.setIsScoAudioConnected(false);
-                    mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
-                            device);
+                    if (audioRouteController.isPending()) {
+                        mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+                                device);
+                    } else {
+                        // Handle case where BT stack signals SCO disconnected but Telecom isn't
+                        // processing any pending routes. This explicitly addresses cf instances
+                        // where a remote device disconnects SCO. Telecom should ensure that audio
+                        // is properly routed in the UI.
+                        audioRouteController.getPendingAudioRoute()
+                                .setCommunicationDeviceType(AudioRoute.TYPE_INVALID);
+                        mCallAudioRouteAdapter.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                                INCLUDE_BLUETOOTH_IN_BASELINE, device.getAddress());
+                    }
                 }  else {
                     mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
                 }
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 3e1da17..99c5746 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -133,9 +133,12 @@
     public static boolean isEnhancedCallBlockingEnabledByPlatform(Context context) {
         CarrierConfigManager configManager = (CarrierConfigManager) context.getSystemService(
                 Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle carrierConfig = configManager.getConfig();
+        PersistableBundle carrierConfig = null;
+        if (configManager != null) {
+            carrierConfig = configManager.getConfig();
+        }
         if (carrierConfig == null) {
-            carrierConfig = configManager.getDefaultConfig();
+            carrierConfig = CarrierConfigManager.getDefaultConfig();
         }
         return carrierConfig.getBoolean(
                 CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL)
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 8c970db..68ffecf 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -114,6 +114,12 @@
                             Log.d(TAG, "processTransaction: call done. id=" + call.getId());
                         }
 
+                        if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
+                            // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
+                            // minute to timeout rather than 5 seconds.
+                            mCallsManager.markCallAsDialing(call);
+                        }
+
                         return CompletableFuture.completedFuture(
                                 new VoipCallTransactionResult(
                                         VoipCallTransactionResult.RESULT_SUCCEED,
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 86d24f9..a6f63bc 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -122,7 +122,7 @@
         doReturn(new ComponentName(mContext, CallTest.class))
                 .when(mMockConnectionService).getComponentName();
         mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
-                mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
+                mFeatureFlags, mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
         mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
index 60952d3..5ccb2fe 100644
--- a/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRecordingTonePlayerTest.java
@@ -42,6 +42,7 @@
 import android.media.MediaRecorder;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.telecom.PhoneAccountHandle;
 
 import androidx.test.filters.MediumTest;
@@ -52,6 +53,7 @@
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -71,6 +73,7 @@
  * Unit tests for the {@link com.android.server.telecom.CallRecordingTonePlayer} class.
  */
 @RunWith(JUnit4.class)
+@RequiresFlagsDisabled(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
 public class CallRecordingTonePlayerTest extends TelecomTestCase {
 
     private static final String PHONE_ACCOUNT_PACKAGE = "com.android.telecom.test";
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
index f2ad2f7..cc1c38a 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
@@ -75,7 +75,7 @@
     mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
     when(mContext.getPackageManager()).thenReturn(mPackageManager);
     mEmergencyCallHelper = new EmergencyCallHelper(mContext, mDefaultDialerCache,
-        mTimeoutsAdapter);
+        mTimeoutsAdapter, mFeatureFlags);
     when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYSTEM_DIALER_PACKAGE);
 
     //start with no perms
@@ -185,6 +185,61 @@
 
   @SmallTest
   @Test
+  public void testPermGrantAndRevokeForEmergencyCall() {
+
+    when(mFeatureFlags.preventRedundantLocationPermissionGrantAndRevoke()).thenReturn(true);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should be granted then revoked
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testPermGrantAndRevokeForMultiEmergencyCall() {
+
+    when(mFeatureFlags.preventRedundantLocationPermissionGrantAndRevoke()).thenReturn(true);
+
+    //first call is emergency call
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    //second call is emergency call
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should be granted then revoked
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testPermGrantAndRevokeForEmergencyCallAndNormalCall() {
+
+    when(mFeatureFlags.preventRedundantLocationPermissionGrantAndRevoke()).thenReturn(true);
+
+    //first call is emergency call
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    //second call is normal call
+    when(mCall.isEmergencyCall()).thenReturn(false);
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should be granted then revoked
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
   public void testNoPermGrantForNonEmergencyCall() {
 
     when(mCall.isEmergencyCall()).thenReturn(false);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 449aa41..bea3fe3 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -230,7 +230,7 @@
                 new ComponentName(SYS_PKG, SYS_CLASS));
         when(mDefaultDialerCache.getBTInCallServicePackages()).thenReturn(new String[] {BT_PKG});
         mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, mDefaultDialerCache,
-                mTimeoutsAdapter);
+                mTimeoutsAdapter, mFeatureFlags);
         when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
         when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index c9faa52..df26684 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
@@ -43,10 +42,10 @@
 import com.android.server.telecom.DockManager;
 import com.android.server.telecom.InCallTonePlayer;
 import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.WiredHeadsetManager;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,7 +63,6 @@
 
     @Mock private BluetoothRouteManager mBluetoothRouteManager;
     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
-    @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothDeviceManager mBluetoothDeviceManager;
     @Mock private TelecomSystem.SyncRoot mLock;
     @Mock private ToneGenerator mToneGenerator;
@@ -73,7 +71,6 @@
     @Mock private DockManager mDockManager;
     @Mock private AsyncRingtonePlayer mRingtonePlayer;
     @Mock private BluetoothDevice mDevice;
-    @Mock private BluetoothAdapter mBluetoothAdapter;
 
     private InCallTonePlayer.MediaPlayerAdapter mMediaPlayerAdapter =
             new InCallTonePlayer.MediaPlayerAdapter() {
@@ -115,7 +112,6 @@
     private CallAudioManager mCallAudioManager;
     @Mock
     private Call mCall;
-
     private InCallTonePlayer mInCallTonePlayer;
 
     @Override
@@ -131,7 +127,7 @@
                 mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager,
                 mDockManager, mRingtonePlayer);
         mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
-                mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
+                mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter, mFeatureFlags);
         mFactory.setCallAudioManager(mCallAudioManager);
         mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_ENDED);
     }
@@ -209,55 +205,92 @@
                 eq(true));
     }
 
+    /**
+     * Only applicable when {@link FeatureFlags#useStreamVoiceCallTones()} is false and we use
+     * STREAM_BLUETOOTH_SCO for tones.
+     */
     @SmallTest
     @Test
     public void testRingbackToneAudioStreamHeadset() {
+        when(mFeatureFlags.useStreamVoiceCallTones()).thenReturn(false);
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
-        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
-        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
-        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
-
-        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
-        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+        setConnectedBluetoothDevice(false /*isLe*/, false /*isHearingAid*/);
 
         mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
         assertTrue(mInCallTonePlayer.startTone());
+
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
         verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 
+    /**
+     * Only applicable when {@link FeatureFlags#useStreamVoiceCallTones()} is false and we use
+     * STREAM_BLUETOOTH_SCO for tones.
+     */
     @SmallTest
     @Test
     public void testCallWaitingToneAudioStreamHeadset() {
+        when(mFeatureFlags.useStreamVoiceCallTones()).thenReturn(false);
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
-        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
-        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
-        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
-
-        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
-        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+        setConnectedBluetoothDevice(false /*isLe*/, false /*isHearingAid*/);
 
         mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
         assertTrue(mInCallTonePlayer.startTone());
+
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
         verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 
+
+    /**
+     * Only applicable when {@link FeatureFlags#useStreamVoiceCallTones()} is true and we use
+     * STREAM_VOICE_CALL for ALL tones.
+     */
+    @SmallTest
+    @Test
+    public void testRingbackToneAudioStreamSco() {
+        when(mFeatureFlags.useStreamVoiceCallTones()).thenReturn(true);
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        setConnectedBluetoothDevice(false /*isLe*/, false /*isHearingAid*/);
+
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
+        assertTrue(mInCallTonePlayer.startTone());
+
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
+    }
+
+    /**
+     * Only applicable when {@link FeatureFlags#useStreamVoiceCallTones()} is true and we use
+     * STREAM_VOICE_CALL for ALL tones.
+     */
+    @SmallTest
+    @Test
+    public void testRingbackToneAudioStreamLe() {
+        when(mFeatureFlags.useStreamVoiceCallTones()).thenReturn(true);
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        setConnectedBluetoothDevice(true /*isLe*/, false /*isHearingAid*/);
+
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
+        assertTrue(mInCallTonePlayer.startTone());
+
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
+    }
+
     @SmallTest
     @Test
     public void testRingbackToneAudioStreamHearingAid() {
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
-        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
-        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
-        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
-
-        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
-        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
+        setConnectedBluetoothDevice(false /*isLe*/, true /*isHearingAid*/);
 
         mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
         assertTrue(mInCallTonePlayer.startTone());
+
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
         verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
@@ -267,17 +300,27 @@
     @Test
     public void testCallWaitingToneAudioStreamHearingAid() {
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        setConnectedBluetoothDevice(false /*isLe*/, true /*isHearingAid*/);
+
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
+        assertTrue(mInCallTonePlayer.startTone());
+
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
+    }
+
+    /**
+     * Set a connected BT device. If not LE or Hearing Aid, it will be configured as SCO
+     * @param isLe true if LE
+     * @param isHearingAid true if hearing aid
+     */
+    private void setConnectedBluetoothDevice(boolean isLe, boolean isHearingAid) {
         mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
         when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
         when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
 
-        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
-        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
-
-        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
-        assertTrue(mInCallTonePlayer.startTone());
-        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
-                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(isLe);
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(isHearingAid);
     }
 }