Merge "Fix broken test and in-call controller bug" into nyc-mr1-dev
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 08f2a98..01a1edf 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -266,7 +266,7 @@
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
         SystemVibrator systemVibrator = new SystemVibrator(context);
         mInCallController = new InCallController(
-                context, mLock, this, systemStateProvider, defaultDialerAdapter);
+                context, mLock, this, systemStateProvider, defaultDialerAdapter, mTimeoutsAdapter);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
                 ringtoneFactory, systemVibrator, mInCallController);
 
@@ -629,7 +629,8 @@
         return false;
     }
 
-    CallAudioState getAudioState() {
+    @VisibleForTesting
+    public CallAudioState getAudioState() {
         return mCallAudioManager.getCallAudioState();
     }
 
@@ -1489,7 +1490,8 @@
     /**
      * Returns true if telecom supports adding another top-level call.
      */
-    boolean canAddCall() {
+    @VisibleForTesting
+    public boolean canAddCall() {
         boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.DEVICE_PROVISIONED, 0) != 0;
         if (!isDeviceProvisioned) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 5e1b887..87eb1d0 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -217,7 +217,7 @@
                     InCallController.this.onConnected(mInCallServiceInfo, service);
             if (!shouldRemainConnected) {
                 // Sometimes we can opt to disconnect for certain reasons, like if the
-                // InCallService rejected our intialization step, or the calls went away
+                // InCallService rejected our initialization step, or the calls went away
                 // in the time it took us to bind to the InCallService. In such cases, we go
                 // ahead and disconnect ourselves.
                 disconnect();
@@ -599,17 +599,19 @@
     private final CallsManager mCallsManager;
     private final SystemStateProvider mSystemStateProvider;
     private final DefaultDialerManagerAdapter mDefaultDialerAdapter;
+    private final Timeouts.Adapter mTimeoutsAdapter;
     private CarSwappingInCallServiceConnection mInCallServiceConnection;
     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
 
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
             SystemStateProvider systemStateProvider,
-            DefaultDialerManagerAdapter defaultDialerAdapter) {
+            DefaultDialerManagerAdapter defaultDialerAdapter, Timeouts.Adapter timeoutsAdapter) {
         mContext = context;
         mLock = lock;
         mCallsManager = callsManager;
         mSystemStateProvider = systemStateProvider;
         mDefaultDialerAdapter = defaultDialerAdapter;
+        mTimeoutsAdapter = timeoutsAdapter;
 
         Resources resources = mContext.getResources();
         mSystemInCallComponentName = new ComponentName(
@@ -671,7 +673,7 @@
                         }
                     }
                 }
-            }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay(
+            }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
                             mContext.getContentResolver()));
         }
         call.removeListener(mCallListener);
@@ -842,10 +844,14 @@
      */
     private void unbindFromServices() {
         if (isBoundToServices()) {
-            mInCallServiceConnection.disconnect();
-            mInCallServiceConnection = null;
-            mNonUIInCallServiceConnections.disconnect();
-            mNonUIInCallServiceConnections = null;
+            if (mInCallServiceConnection != null) {
+                mInCallServiceConnection.disconnect();
+                mInCallServiceConnection = null;
+            }
+            if (mNonUIInCallServiceConnections != null) {
+                mNonUIInCallServiceConnections.disconnect();
+                mNonUIInCallServiceConnections = null;
+            }
         }
     }
 
@@ -1079,34 +1085,32 @@
 
         // Upon successful connection, send the state of the world to the service.
         List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
-        if (!calls.isEmpty()) {
-            Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
-                    info.getComponentName());
-            for (Call call : calls) {
-                try {
-                    if (call.isExternalCall() && !info.isExternalCallsSupported()) {
-                        continue;
-                    }
-
-                    // Track the call if we don't already know about it.
-                    addCall(call);
-
-                    inCallService.addCall(ParcelableCallUtils.toParcelableCall(
-                            call,
-                            true /* includeVideoProvider */,
-                            mCallsManager.getPhoneAccountRegistrar(),
-                            info.isExternalCallsSupported()));
-                } catch (RemoteException ignored) {
-                }
-            }
+        Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
+                "calls", calls.size(), info.getComponentName());
+        int numCallsSent = 0;
+        for (Call call : calls) {
             try {
-                inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
-                inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
+                if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+                    continue;
+                }
+
+                // Track the call if we don't already know about it.
+                addCall(call);
+                numCallsSent += 1;
+                inCallService.addCall(ParcelableCallUtils.toParcelableCall(
+                        call,
+                        true /* includeVideoProvider */,
+                        mCallsManager.getPhoneAccountRegistrar(),
+                        info.isExternalCallsSupported()));
             } catch (RemoteException ignored) {
             }
-        } else {
-            return false;
         }
+        try {
+            inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
+            inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
+        } catch (RemoteException ignored) {
+        }
+        Log.i(this, "%s calls sent to InCallService.", numCallsSent);
         Trace.endSection();
         return true;
     }
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index b64ce9c..2061ce4 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -97,7 +97,7 @@
         }
 
         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
-        boolean isDefaultSmsAccount =
+        boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
                 phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
         if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
             capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 7026084..7be59c3 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -32,6 +32,10 @@
         public long getCallScreeningTimeoutMillis(ContentResolver cr) {
             return Timeouts.getCallScreeningTimeoutMillis(cr);
         }
+
+        public long getCallRemoveUnbindInCallServicesDelay(ContentResolver cr) {
+            return Timeouts.getCallRemoveUnbindInCallServicesDelay(cr);
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -53,15 +57,6 @@
     }
 
     /**
-     * Returns the longest period, in milliseconds, to wait for the query for direct-to-voicemail
-     * to complete. If the query goes beyond this timeout, the incoming call screen is shown to the
-     * user.
-     */
-    public static long getDirectToVoicemailMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "direct_to_voicemail_ms", 500L);
-    }
-
-    /**
      * Returns the amount of time to wait before disconnecting a call that was canceled via
      * NEW_OUTGOING_CALL broadcast. This timeout allows apps which repost the call using a gateway
      * to reuse the existing call, preventing the call from causing a start->end->start jank in the
@@ -141,11 +136,4 @@
     public static long getCallScreeningTimeoutMillis(ContentResolver contentResolver) {
         return get(contentResolver, "call_screening_timeout", 5000L /* 5 seconds */);
     }
-
-    /**
-     * Returns the amount of time to wait for the block checker to allow or disallow a call.
-     */
-    public static long getBlockCheckTimeoutMillis(ContentResolver contentResolver) {
-        return get(contentResolver, "block_check_timeout_millis", 500L);
-    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 0173ed8..cf72225 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
@@ -29,6 +30,7 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.telecom.InCallService;
+import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.mock.MockContext;
@@ -46,6 +48,7 @@
 import com.android.server.telecom.SystemStateProvider;
 import com.android.server.telecom.TelecomServiceImpl.DefaultDialerManagerAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
@@ -53,6 +56,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.util.Collections;
 import java.util.LinkedList;
 
 import static org.mockito.Matchers.any;
@@ -79,6 +83,7 @@
     @Mock Resources mMockResources;
     @Mock MockContext mMockContext;
     @Mock DefaultDialerManagerAdapter mMockDefaultDialerAdapter;
+    @Mock Timeouts.Adapter mTimeoutsAdapter;
 
     private static final int CURRENT_USER_ID = 900973;
     private static final String DEF_PKG = "defpkg";
@@ -100,7 +105,7 @@
         doReturn(SYS_PKG).when(mMockResources).getString(R.string.ui_default_package);
         doReturn(SYS_CLASS).when(mMockResources).getString(R.string.incall_default_class);
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
-                mMockSystemStateProvider, mMockDefaultDialerAdapter);
+                mMockSystemStateProvider, mMockDefaultDialerAdapter, mTimeoutsAdapter);
     }
 
     @Override
@@ -278,10 +283,14 @@
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        when(mMockCallsManager.getAudioState()).thenReturn(null);
+        when(mMockCallsManager.canAddCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getConferenceableCalls()).thenReturn(Collections.emptyList());
         when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(
@@ -386,6 +395,61 @@
         assertEquals(DEF_CLASS, bindIntent.getComponent().getClassName());
     }
 
+    /**
+     * Make sure that if a call goes away before the in-call service finishes binding and another
+     * call gets connected soon after, the new call will still be sent to the in-call service.
+     */
+    @MediumTest
+    public void testUnbindDueToCallDisconnect() throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.hasEmergencyCall()).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockDefaultDialerAdapter.getDefaultDialerApplication(mMockContext, CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        when(mMockContext.bindServiceAsUser(
+                any(Intent.class), any(ServiceConnection.class), anyInt(), any(UserHandle.class)))
+                .thenReturn(true);
+        when(mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(any(ContentResolver.class)))
+                .thenReturn(500L);
+
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                serviceConnectionCaptor.capture(),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        // Pretend that the call has gone away.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+
+        // Start the connection, make sure we don't unbind, and make sure that we don't send
+        // anything to the in-call service yet.
+        ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+        ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
+        IBinder mockBinder = mock(IBinder.class);
+        IInCallService mockInCallService = mock(IInCallService.class);
+        when(mockBinder.queryLocalInterface(anyString())).thenReturn(mockInCallService);
+
+        serviceConnection.onServiceConnected(defDialerComponentName, mockBinder);
+        verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
+        verify(mMockContext, never()).unbindService(serviceConnection);
+        verify(mockInCallService, never()).addCall(any(ParcelableCall.class));
+
+        // Now, we add in the call again and make sure that it's sent to the InCallService.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
+        mInCallController.onCallAdded(mMockCall);
+        verify(mockInCallService).addCall(any(ParcelableCall.class));
+    }
+
     private void setupMocks(boolean isExternalCall) {
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);