Merge "Improve headset media button logging in Telecom." into main
diff --git a/flags/telecom_non_critical_security_flags.aconfig b/flags/telecom_non_critical_security_flags.aconfig
index 37929a8..e492073 100644
--- a/flags/telecom_non_critical_security_flags.aconfig
+++ b/flags/telecom_non_critical_security_flags.aconfig
@@ -7,4 +7,15 @@
   namespace: "telecom"
   description: "When set, Telecom will unregister accounts if the service is not resolvable"
   bug: "281061708"
+}
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+  name: "enforce_transactional_exclusivity"
+  namespace: "telecom"
+  description: "When set, ensure that transactional accounts cannot also be call capable"
+  bug: "376936125"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index d2ac13a..ba75d0c 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -100,7 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Padovi aplikacija za telefon"</string>
-    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Prijenos poziva"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Prenos poziva"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Upućivanje ovog poziva će prekinuti poziv: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Odaberite kako želite uputiti ovaj poziv"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmjeri poziv pomoću aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -131,7 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Vanjski"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
-    <string name="call_streaming_notification_body" msgid="502216105683378263">"Prijenos zvuka na drugom uređaju"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Prenos zvuka na drugom uređaju"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prebaci ovdje"</string>
 </resources>
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index a2998fb..bda5063 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -1581,8 +1581,16 @@
                         mHandler.postDelayed(new Runnable("ICC.oDCTP", mLock) {
                             @Override
                             public void loggedRun() {
-                                Log.i(this, "onDisconnectedTonePlaying: unbinding");
-                                connection.disconnect();
+                                Log.i(this, "onDisconnectedTonePlaying: unbinding from BT ICS.");
+                                // Prevent unbinding in the case that this is run while another call
+                                // has been placed/received. Otherwise, we will early unbind from
+                                // the BT ICS and not be able to properly relay call state updates.
+                                if (!mBTInCallServiceConnections.containsKey(userHandle)) {
+                                    connection.disconnect();
+                                } else {
+                                    Log.i(this, "onDisconnectedTonePlaying: Refraining from "
+                                            + "unbinding BT ICS. Another call is ongoing.");
+                                }
                             }
                         }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                                 mContext.getContentResolver()));
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index f0423c3..796a62b 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1284,12 +1284,15 @@
         boolean isNewAccount;
 
         // add self-managed capability for transactional accounts that are missing it
-        if (hasTransactionalCallCapabilities(account) &&
-                !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+        if (hasTransactionalCallCapabilities(account)
+                && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
             account = account.toBuilder()
                     .setCapabilities(account.getCapabilities()
                             | PhoneAccount.CAPABILITY_SELF_MANAGED)
                     .build();
+            // Note: below we will automatically remove CAPABILITY_CONNECTION_MANAGER,
+            // CAPABILITY_CALL_PROVIDER, and CAPABILITY_SIM_SUBSCRIPTION if this magically becomes
+            // a self-managed phone account here.
         }
 
         PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
@@ -1310,6 +1313,12 @@
         if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
             // Turn off bits we don't want to be able to set (TelecomServiceImpl protects against
             // this but we'll also prevent it from happening here, just to be safe).
+            if ((account.getCapabilities() & (PhoneAccount.CAPABILITY_CALL_PROVIDER
+                    | PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+                    | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) > 0) {
+                Log.w(this, "addOrReplacePhoneAccount: attempt to register a "
+                        + "VoIP phone account with call provider/cm/sim sub capabilities.");
+            }
             int newCapabilities = account.getCapabilities() &
                     ~(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_CONNECTION_MANAGER |
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 1075ff7..7ec2687 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -882,16 +882,20 @@
                     try {
                         enforcePhoneAccountModificationForPackage(
                                 account.getAccountHandle().getComponentName().getPackageName());
-                        if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+                        if (account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+                                || (mFeatureFlags.enforceTransactionalExclusivity()
+                                && account.hasCapabilities(
+                                PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS))) {
                             enforceRegisterSelfManaged();
                             if (account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER) ||
                                     account.hasCapabilities(
                                             PhoneAccount.CAPABILITY_CONNECTION_MANAGER) ||
                                     account.hasCapabilities(
                                             PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
-                                throw new SecurityException("Self-managed ConnectionServices " +
-                                        "cannot also be call capable, connection managers, or " +
-                                        "SIM accounts.");
+                                throw new SecurityException("Self-managed ConnectionServices and "
+                                        + "transactional voip apps "
+                                        + "cannot also be call capable, connection managers, or "
+                                        + "SIM accounts.");
                             }
 
                             // For self-managed CS, the phone account registrar will override the
diff --git a/testapps/transactionalVoipApp/res/values-bs/strings.xml b/testapps/transactionalVoipApp/res/values-bs/strings.xml
index 24ffba2..f417043 100644
--- a/testapps/transactionalVoipApp/res/values-bs/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bs/strings.xml
@@ -31,7 +31,7 @@
     <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
     <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
     <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
-    <string name="start_stream" msgid="3567634786280097431">"pokreni prijenos"</string>
+    <string name="start_stream" msgid="3567634786280097431">"pokreni prenos"</string>
     <string name="crash_app" msgid="2548690390730057704">"izbaci izuzetak"</string>
     <string name="update_notification" msgid="8677916482672588779">"ažuriraj obavještenje u stil poziva u toku"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-in/strings.xml b/testapps/transactionalVoipApp/res/values-in/strings.xml
index 935f036..ba41376 100644
--- a/testapps/transactionalVoipApp/res/values-in/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-in/strings.xml
@@ -27,7 +27,7 @@
     <string name="set_call_active" msgid="3365404393507589899">"setelAktif"</string>
     <string name="answer" msgid="5423590397665409939">"jawab"</string>
     <string name="set_call_inactive" msgid="7106775211368705195">"setelNonaktif"</string>
-    <string name="disconnect_call" msgid="1349412380315371385">"putuskan koneksi"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"berhenti hubungkan"</string>
     <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
     <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
     <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
diff --git a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
index fd33afa..631d522 100644
--- a/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionManagerTest.java
@@ -61,13 +61,11 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mTestSessionManager = new SessionManager();
+        mTestSessionManager = new SessionManager(null);
         mTestSessionManager.registerSessionListener(((sessionName, timeMs) -> {
             mfullSessionCompleteTime = timeMs;
             mFullSessionMethodName = sessionName;
         }));
-        // Remove automatic stale session cleanup for testing
-        mTestSessionManager.mCleanStaleSessions = null;
     }
 
     @Override
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index e8389a0..5b5c3ed 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -51,8 +51,8 @@
 
         mComponentContextFixture = new ComponentContextFixture(mFeatureFlags);
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        Log.setSessionContext(mComponentContextFixture.getTestDouble().getApplicationContext());
-        Log.getSessionManager().mCleanStaleSessions = null;
+        Log.setSessionManager(mComponentContextFixture.getTestDouble().getApplicationContext(),
+                null);
     }
 
     public void tearDown() throws Exception {