Merge "Move carrier in-service check to after E911 timeout." into main
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 872b423..29eb419 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -2550,7 +2550,7 @@
             return;
         }
         mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
-                phoneAccountRegistrar, mContext, mFlags);
+                phoneAccountRegistrar, mContext, mFlags, new Timeouts.Adapter());
         mCreateConnectionProcessor.process();
     }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 607aa27..f6f4889 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -2032,7 +2032,8 @@
     }
 
     /** @see IConnectionService#disconnect(String, Session.Info) */
-    void disconnect(Call call) {
+    @VisibleForTesting
+    public void disconnect(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("disconnect")) {
             try {
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index abb2d8c..a2c742d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -138,15 +138,19 @@
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final Context mContext;
     private final FeatureFlags mFlags;
+    private final Timeouts.Adapter mTimeoutsAdapter;
     private CreateConnectionTimeout mTimeout;
     private ConnectionServiceWrapper mService;
     private int mConnectionAttempt;
 
     @VisibleForTesting
-    public CreateConnectionProcessor(
-            Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
-            PhoneAccountRegistrar phoneAccountRegistrar, Context context,
-            FeatureFlags featureFlags) {
+    public CreateConnectionProcessor(Call call,
+            ConnectionServiceRepository repository,
+            CreateConnectionResponse response,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            Context context,
+            FeatureFlags featureFlags,
+            Timeouts.Adapter timeoutsAdapter) {
         Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
         mCall = call;
         mRepository = repository;
@@ -155,6 +159,7 @@
         mContext = context;
         mConnectionAttempt = 0;
         mFlags = featureFlags;
+        mTimeoutsAdapter = timeoutsAdapter;
     }
 
     boolean isProcessingComplete() {
@@ -327,7 +332,7 @@
         clearTimeout();
 
         CreateConnectionTimeout timeout = new CreateConnectionTimeout(
-                mContext, mPhoneAccountRegistrar, service, mCall);
+                mContext, mPhoneAccountRegistrar, service, mCall, mTimeoutsAdapter);
         if (timeout.isTimeoutNeededForCall(getConnectionServices(mAttemptRecords),
                 attempt.connectionManagerPhoneAccount)) {
             mTimeout = timeout;
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 904ec81..7615d21 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -44,15 +44,17 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private boolean mIsRegistered;
     private boolean mIsCallTimedOut;
+    private final Timeouts.Adapter mTimeoutsAdapter;
 
     @VisibleForTesting
     public CreateConnectionTimeout(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
-            ConnectionServiceWrapper service, Call call) {
+            ConnectionServiceWrapper service, Call call, Timeouts.Adapter timeoutsAdapter) {
         super("CCT", null /*lock*/);
         mContext = context;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mConnectionService = service;
         mCall = call;
+        mTimeoutsAdapter = timeoutsAdapter;
     }
 
     @VisibleForTesting
@@ -60,6 +62,7 @@
             PhoneAccountHandle currentAccount) {
         // Non-emergency calls timeout automatically at the radio layer. No need for a timeout here.
         if (!mCall.isEmergencyCall()) {
+            Log.d(this, "isTimeoutNeededForCall, not an emergency call");
             return false;
         }
 
@@ -68,11 +71,13 @@
         PhoneAccountHandle connectionManager =
                 mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall);
         if (!accounts.contains(connectionManager)) {
+            Log.d(this, "isTimeoutNeededForCall, no connection manager");
             return false;
         }
 
         // No need to add a timeout if the current attempt is over the connection manager.
         if (Objects.equals(connectionManager, currentAccount)) {
+            Log.d(this, "isTimeoutNeededForCall, already attempting over connection manager");
             return false;
         }
 
@@ -83,17 +88,6 @@
             return false;
         }
 
-        // Timeout is not required if carrier is not in service.
-        if (carrierEnabledSatelliteFlag() && connectionManager != null) {
-            PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(connectionManager,
-                    connectionManager.getUserHandle());
-            if (account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)
-                    && !account.hasCapabilities(PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) {
-                Log.d(this, "isTimeoutNeededForCall, carrier is not in service.");
-                return false;
-            }
-        }
-
         Log.i(this, "isTimeoutNeededForCall, returning true");
         return true;
     }
@@ -123,8 +117,31 @@
 
     @Override
     public void loggedRun() {
+        if (!carrierEnabledSatelliteFlag()) {
+            timeoutCallIfNeeded();
+            return;
+        }
+
+        PhoneAccountHandle connectionManager =
+                mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall);
+        if (connectionManager != null) {
+            PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(connectionManager,
+                    connectionManager.getUserHandle());
+            if (account != null && account.hasCapabilities(
+                    (PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
+                            | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE))) {
+                // If we have encountered the timeout and there is an in service
+                // ConnectionManager, disconnect the call so that it can be attempted over
+                // the ConnectionManager.
+                timeoutCallIfNeeded();
+                return;
+            }
+        }
+    }
+
+    private void timeoutCallIfNeeded() {
         if (mIsRegistered && isCallBeingPlaced(mCall)) {
-            Log.i(this, "run, call timed out, calling disconnect");
+            Log.i(this, "timeoutCallIfNeeded, call timed out, calling disconnect");
             mIsCallTimedOut = true;
             mConnectionService.disconnect(mCall);
         }
@@ -145,14 +162,15 @@
             TelephonyManager telephonyManager =
                     (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
             if (telephonyManager.isRadioOn()) {
-                return Timeouts.getEmergencyCallTimeoutMillis(mContext.getContentResolver());
+                return mTimeoutsAdapter.getEmergencyCallTimeoutMillis(
+                        mContext.getContentResolver());
             } else {
-                return Timeouts.getEmergencyCallTimeoutRadioOffMillis(
+                return mTimeoutsAdapter.getEmergencyCallTimeoutRadioOffMillis(
                         mContext.getContentResolver());
             }
         } catch (UnsupportedOperationException uoe) {
             Log.e(this, uoe, "getTimeoutLengthMillis - telephony is not supported");
-            return Timeouts.getEmergencyCallTimeoutMillis(mContext.getContentResolver());
+            return mTimeoutsAdapter.getEmergencyCallTimeoutMillis(mContext.getContentResolver());
         }
     }
 }
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index abc7ff6..0ed71df 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -61,6 +61,14 @@
             return Timeouts.getEmergencyCallbackWindowMillis(cr);
         }
 
+        public long getEmergencyCallTimeoutMillis(ContentResolver cr) {
+            return Timeouts.getEmergencyCallTimeoutMillis(cr);
+        }
+
+        public long getEmergencyCallTimeoutRadioOffMillis(ContentResolver cr) {
+            return Timeouts.getEmergencyCallTimeoutRadioOffMillis(cr);
+        }
+
         public long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver cr) {
             return Timeouts.getUserDefinedCallRedirectionTimeoutMillis(cr);
         }
@@ -127,7 +135,6 @@
 
         public int getDaysBackToSearchEmergencyDiagnosticEntries(){
             return Timeouts.getDaysBackToSearchEmergencyDiagnosticEntries();
-
         }
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index 5bfacaa..ddbc250 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -21,11 +21,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -50,6 +50,7 @@
 import com.android.internal.telephony.flags.Flags;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallIdMapper;
+import com.android.server.telecom.CallState;
 import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.ConnectionServiceRepository;
 import com.android.server.telecom.ConnectionServiceWrapper;
@@ -57,7 +58,7 @@
 import com.android.server.telecom.CreateConnectionResponse;
 import com.android.server.telecom.CreateConnectionTimeout;
 import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.Timeouts;
 
 import org.junit.After;
 import org.junit.Before;
@@ -70,6 +71,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -98,6 +100,7 @@
     Call mMockCall;
     @Mock
     ConnectionServiceFocusManager mConnectionServiceFocusManager;
+    @Mock Timeouts.Adapter mTimeoutsAdapter;
 
     CreateConnectionProcessor mTestCreateConnectionProcessor;
     CreateConnectionTimeout mTestCreateConnectionTimeout;
@@ -131,7 +134,7 @@
 
         mTestCreateConnectionProcessor = new CreateConnectionProcessor(mMockCall,
                 mMockConnectionServiceRepository, mMockCreateConnectionResponse,
-                mMockAccountRegistrar, mContext, mFeatureFlags);
+                mMockAccountRegistrar, mContext, mFeatureFlags, mTimeoutsAdapter);
 
         mAccountToSub = new HashMap<>();
         phoneAccounts = new ArrayList<>();
@@ -156,7 +159,7 @@
                 thenReturn(Binder.getCallingUserHandle());
 
         mTestCreateConnectionTimeout = new CreateConnectionTimeout(mContext, mMockAccountRegistrar,
-                makeConnectionServiceWrapper(), mMockCall);
+                makeConnectionServiceWrapper(), mMockCall, mTimeoutsAdapter);
 
         mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG);
     }
@@ -786,30 +789,6 @@
     }
 
     @Test
-    public void testIsTimeoutNeededForCall_carrierNotInService() {
-        when(mMockCall.isEmergencyCall()).thenReturn(true);
-        when(mMockAccountRegistrar.getSystemSimCallManagerComponent()).thenReturn(
-                new ComponentName(TEST_PACKAGE, TEST_CLASS));
-
-        List<PhoneAccountHandle> phoneAccountHandles = new ArrayList<>();
-        // Put in a regular phone account handle
-        PhoneAccount regularAccount = makePhoneAccount("tel_acct1",
-                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
-        phoneAccountHandles.add(regularAccount.getAccountHandle());
-        // Create a connection manager for the call and include it in phoneAccountHandles
-        int capability = PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS;
-        PhoneAccount callManagerPA = createNewConnectionManagerPhoneAccountForCall(mMockCall,
-                "cm_acct", capability);
-        PhoneAccount phoneAccountWithoutService = makeQuickAccount("cm_acct", capability, null);
-        when(mMockAccountRegistrar.getPhoneAccount(callManagerPA.getAccountHandle(),
-                callManagerPA.getAccountHandle().getUserHandle()))
-                .thenReturn(phoneAccountWithoutService);
-        phoneAccountHandles.add(callManagerPA.getAccountHandle());
-
-        assertFalse(mTestCreateConnectionTimeout.isTimeoutNeededForCall(phoneAccountHandles, null));
-    }
-
-    @Test
     public void testIsTimeoutNeededForCall() {
         when(mMockCall.isEmergencyCall()).thenReturn(true);
         when(mMockAccountRegistrar.getSystemSimCallManagerComponent()).thenReturn(
@@ -834,6 +813,79 @@
         assertTrue(mTestCreateConnectionTimeout.isTimeoutNeededForCall(phoneAccountHandles, null));
     }
 
+    @Test
+    public void testConnTimeout_carrierSatelliteEnabled_noInServiceConnManager_callNeverTimesOut() {
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+        when(mMockCall.getHandle()).thenReturn(Uri.parse(""));
+        when(mMockCall.getState()).thenReturn(CallState.DIALING);
+        // Primary phone account, meant to fail
+        PhoneAccount regularAccount = makePhoneAccount("tel_acct1",
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                        | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+        phoneAccounts.add(regularAccount);
+        when(mMockAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+                nullable(String.class))).thenReturn(regularAccount.getAccountHandle());
+        when(mMockAccountRegistrar.getSystemSimCallManagerComponent()).thenReturn(
+                new ComponentName(TEST_PACKAGE, TEST_CLASS));
+        PhoneAccount callManagerPA = getNewEmergencyConnectionManagerPhoneAccount(
+                "cm_acct", PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS
+                        | PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS);
+        phoneAccounts.add(callManagerPA);
+        ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+        when(mMockAccountRegistrar.getSimCallManagerFromCall(mMockCall)).thenReturn(
+                callManagerPA.getAccountHandle());
+        when(mMockAccountRegistrar.getPhoneAccount(eq(callManagerPA.getAccountHandle()),
+                any())).thenReturn(callManagerPA);
+        Duration timeout = Duration.ofMillis(10);
+        when(mTimeoutsAdapter.getEmergencyCallTimeoutMillis(any())).thenReturn(timeout.toMillis());
+        when(mTimeoutsAdapter.getEmergencyCallTimeoutRadioOffMillis(any())).thenReturn(
+                timeout.toMillis());
+
+
+        mTestCreateConnectionProcessor.process();
+
+        // Validate the call is not disconnected after the timeout.
+        verify(service, after(timeout.toMillis() + 100).never()).disconnect(eq(mMockCall));
+    }
+
+    @Test
+    public void testConnTimeout_carrierSatelliteEnabled_inServiceConnManager_callTimesOut() {
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+        when(mMockCall.getHandle()).thenReturn(Uri.parse(""));
+        when(mMockCall.getState()).thenReturn(CallState.DIALING);
+        // Primary phone account, meant to fail
+        PhoneAccount regularAccount = makePhoneAccount("tel_acct1",
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                        | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+        phoneAccounts.add(regularAccount);
+        when(mMockAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+                nullable(String.class))).thenReturn(regularAccount.getAccountHandle());
+        when(mMockAccountRegistrar.getSystemSimCallManagerComponent()).thenReturn(
+                new ComponentName(TEST_PACKAGE, TEST_CLASS));
+        PhoneAccount callManagerPA = getNewEmergencyConnectionManagerPhoneAccount(
+                "cm_acct", PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS
+                        | PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
+                        | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE
+        );
+        phoneAccounts.add(callManagerPA);
+        ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+        when(mMockAccountRegistrar.getSimCallManagerFromCall(mMockCall)).thenReturn(
+                callManagerPA.getAccountHandle());
+        when(mMockAccountRegistrar.getPhoneAccount(eq(callManagerPA.getAccountHandle()),
+                any())).thenReturn(callManagerPA);
+        Duration timeout = Duration.ofMillis(10);
+        when(mTimeoutsAdapter.getEmergencyCallTimeoutMillis(any())).thenReturn(timeout.toMillis());
+        when(mTimeoutsAdapter.getEmergencyCallTimeoutRadioOffMillis(any())).thenReturn(
+                timeout.toMillis());
+
+        mTestCreateConnectionProcessor.process();
+
+        // Validate the call was disconnected after the timeout.
+        verify(service, after(timeout.toMillis() + 100)).disconnect(eq(mMockCall));
+    }
+
     /**
      * Verifies when telephony is not available that we just get invalid sub id for a phone acct.
      */