Add tests for InCallPresenter

Add a bunch of end-to-end tests for InCallPresenter

Refactor InCallPresenter to be more dependency injection
friendly.

Add some test-only methods to make testing easier

Change-Id: I86b6eeff91d35bc3b5cb3de9262d8850673919b7
diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java
index c823fda..a267661 100644
--- a/InCallUI/src/com/android/incallui/AudioModeProvider.java
+++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java
@@ -26,7 +26,7 @@
 /**
  * Proxy class for getting and setting the audio mode.
  */
-/* package */ class AudioModeProvider implements InCallPhoneListener {
+public class AudioModeProvider implements InCallPhoneListener {
 
     static final int AUDIO_MODE_INVALID = 0;
 
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index 663c412..16e4a2c 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -17,6 +17,7 @@
 package com.android.incallui;
 
 import com.android.contacts.common.CallUtil;
+import com.android.contacts.common.testing.NeededForTesting;
 
 import android.content.Context;
 import android.net.Uri;
@@ -187,6 +188,16 @@
 
     private InCallVideoCallListener mVideoCallListener;
 
+    /**
+     * Used only to create mock calls for testing
+     */
+    @NeededForTesting
+    Call(int state) {
+        mTelecommCall = null;
+        mId = ID_PREFIX + Integer.toString(sIdCounter++);
+        setState(state);
+    }
+
     public Call(android.telecom.Call telecommCall) {
         mTelecommCall = telecommCall;
         mId = ID_PREFIX + Integer.toString(sIdCounter++);
@@ -270,7 +281,7 @@
     }
 
     public int getState() {
-        if (mTelecommCall.getParent() != null) {
+        if (mTelecommCall != null && mTelecommCall.getParent() != null) {
             return State.CONFERENCED;
         } else {
             return mState;
@@ -401,6 +412,12 @@
 
     @Override
     public String toString() {
+        if (mTelecommCall == null) {
+            // This should happen only in testing since otherwise we would never have a null
+            // Telecom call.
+            return String.valueOf(mId);
+        }
+
         return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " +
                 "videoState:%d]",
                 mId,
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 0343ed6..e90dc99 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -37,6 +37,7 @@
 import com.google.common.base.Preconditions;
 
 import com.android.contacts.common.interactions.TouchPointManager;
+import com.android.contacts.common.testing.NeededForTesting;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 import com.android.incalluibind.ObjectFactory;
 
@@ -187,6 +188,11 @@
         return sInCallPresenter;
     }
 
+    @NeededForTesting
+    static synchronized void setInstance(InCallPresenter inCallPresenter) {
+        sInCallPresenter = inCallPresenter;
+    }
+
     @Override
     public void setPhone(Phone phone) {
         mPhone = phone;
@@ -207,7 +213,12 @@
         return mCallList;
     }
 
-    public void setUp(Context context, CallList callList, AudioModeProvider audioModeProvider) {
+    public void setUp(Context context,
+            CallList callList,
+            AudioModeProvider audioModeProvider,
+            StatusBarNotifier statusBarNotifier,
+            ContactInfoCache contactInfoCache,
+            ProximitySensor proximitySensor) {
         if (mServiceConnected) {
             Log.i(this, "New service connection replacing existing one.");
             // retain the current resources, no need to create new ones.
@@ -220,14 +231,14 @@
         Preconditions.checkNotNull(context);
         mContext = context;
 
-        mContactInfoCache = ContactInfoCache.getInstance(context);
+        mContactInfoCache = contactInfoCache;
 
-        mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache);
+        mStatusBarNotifier = statusBarNotifier;
         addListener(mStatusBarNotifier);
 
         mAudioModeProvider = audioModeProvider;
 
-        mProximitySensor = new ProximitySensor(context, mAudioModeProvider);
+        mProximitySensor = proximitySensor;
         addListener(mProximitySensor);
 
         mCallList = callList;
@@ -918,8 +929,9 @@
         //          [ AND NOW YOU'RE IN THE CALL. voila! ]
         //
         // Our app is started using a fullScreen notification.  We need to do this whenever
-        // we get an incoming call.
-        final boolean startStartupSequence = (InCallState.INCOMING == newState);
+        // we get an incoming call. Depending on the current context of the device, either a
+        // incoming call HUN or the actual InCallActivity will be shown.
+        final boolean startIncomingCallSequence = (InCallState.INCOMING == newState);
 
         // A dialog to show on top of the InCallUI to select a PhoneAccount
         final boolean showAccountPicker = (InCallState.WAITING_FOR_ACCOUNT == newState);
@@ -967,7 +979,7 @@
         if (showCallUi || showAccountPicker) {
             Log.i(this, "Start in call UI");
             showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
-        } else if (startStartupSequence) {
+        } else if (startIncomingCallSequence) {
             Log.i(this, "Start Full Screen in call UI");
 
             // We're about the bring up the in-call UI for an incoming call. If we still have
diff --git a/InCallUI/src/com/android/incallui/InCallServiceImpl.java b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
index 17f4e17..adb0697 100644
--- a/InCallUI/src/com/android/incallui/InCallServiceImpl.java
+++ b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.incallui;
 
+import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
 import android.telecom.InCallService;
@@ -53,10 +54,16 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        final Context context = getApplicationContext();
+        final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
         InCallPresenter.getInstance().setUp(
                 getApplicationContext(),
                 CallList.getInstance(),
-                AudioModeProvider.getInstance());
+                AudioModeProvider.getInstance(),
+                new StatusBarNotifier(context, contactInfoCache),
+                contactInfoCache,
+                new ProximitySensor(context, AudioModeProvider.getInstance())
+                );
         InCallPresenter.getInstance().onServiceBind();
         InCallPresenter.getInstance().maybeStartRevealAnimation(intent);
         return super.onBind(intent);
diff --git a/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java b/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java
new file mode 100644
index 0000000..6d60876
--- /dev/null
+++ b/InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.test.InstrumentationTestCase;
+
+import com.android.incallui.InCallPresenter.InCallState;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+public class InCallPresenterTest extends InstrumentationTestCase {
+    private static final Call TEST_INCOMING_CALL = new Call(Call.State.INCOMING);
+    private static final Call TEST_ACTIVE_CALL = new Call(Call.State.ACTIVE);
+    private static final Call TEST_PENDING_CALL = new Call(Call.State.CONNECTING);
+    private static final Call TEST_WAITING_FOR_ACCOUNT_CALL = new Call(Call.State.PRE_DIAL_WAIT);
+
+    @Mock private InCallActivity mInCallActivity;
+    @Mock private AudioModeProvider mAudioModeProvider;
+    @Mock private CallList mCallList;
+    @Mock private StatusBarNotifier mStatusBarNotifier;
+    @Mock private ContactInfoCache mContactInfoCache;
+    @Mock private ProximitySensor mProximitySensor;
+
+    InCallPresenter mInCallPresenter;
+    @Mock private Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        System.setProperty("dexmaker.dexcache",
+                getInstrumentation().getTargetContext().getCacheDir().getPath());
+        MockitoAnnotations.initMocks(this);
+        mInCallPresenter = InCallPresenter.getInstance();
+        mInCallPresenter.setUp(mContext, mCallList, mAudioModeProvider, mStatusBarNotifier,
+                mContactInfoCache, mProximitySensor);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // The tear down method needs to run in the main thread since there is an explicit check
+        // inside TelecomAdapter.getInstance().
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mInCallPresenter.unsetActivity(mInCallActivity);
+                mInCallPresenter.tearDown();
+                InCallPresenter.setInstance(null);
+            }
+        });
+    }
+
+    public void testOnActivitySet_finishesActivityWhenNoCalls() {
+        mInCallPresenter.setActivity(mInCallActivity);
+
+        verify(mInCallActivity).finish();
+    }
+
+    public void testOnCallListChange_sendsNotificationWhenInCall() {
+        when(mCallList.getIncomingCall()).thenReturn(TEST_INCOMING_CALL);
+
+        mInCallPresenter.onCallListChange(mCallList);
+
+        verify(mStatusBarNotifier).updateNotification(InCallState.INCOMING, mCallList);
+        verifyInCallActivityNotStarted();
+    }
+
+    /**
+     * This behavior is required to ensure that the screen is turned on again by the restarting
+     * activity.
+     */
+    public void testOnCallListChange_handlesCallWaitingWhenScreenOffShouldRestartActivity() {
+        when(mCallList.getActiveCall()).thenReturn(TEST_ACTIVE_CALL);
+
+        mInCallPresenter.onCallListChange(mCallList);
+        mInCallPresenter.setActivity(mInCallActivity);
+
+        // Pretend that there is a call waiting and the screen is off
+        when(mInCallActivity.isDestroyed()).thenReturn(false);
+        when(mInCallActivity.isFinishing()).thenReturn(false);
+        when(mProximitySensor.isScreenReallyOff()).thenReturn(true);
+        when(mCallList.getIncomingCall()).thenReturn(TEST_INCOMING_CALL);
+
+        mInCallPresenter.onCallListChange(mCallList);
+        verify(mInCallActivity).finish();
+    }
+
+    /**
+     * Verifies that the PENDING_OUTGOING -> IN_CALL transition brings up InCallActivity so
+     * that it can display an error dialog.
+     */
+    public void testOnCallListChange_pendingOutgoingToInCallTransitionShowsUiForErrorDialog() {
+        when(mCallList.getPendingOutgoingCall()).thenReturn(TEST_PENDING_CALL);
+
+        mInCallPresenter.onCallListChange(mCallList);
+
+        when(mCallList.getPendingOutgoingCall()).thenReturn(null);
+        when(mCallList.getActiveCall()).thenReturn(TEST_ACTIVE_CALL);
+
+        mInCallPresenter.onCallListChange(mCallList);
+
+        verify(mContext).startActivity(InCallPresenter.getInstance().getInCallIntent(false, false));
+        verifyIncomingCallNotificationNotSent();
+    }
+
+    /**
+     * Verifies that if there is a call in the PRE_DIAL_WAIT state, InCallActivity is displayed
+     * to display the account picker.
+     */
+    public void testOnCallListChange_noAccountProvidedForCallShowsUiForAccountPicker() {
+        when(mCallList.getWaitingForAccountCall()).thenReturn(TEST_WAITING_FOR_ACCOUNT_CALL);
+        mInCallPresenter.onCallListChange(mCallList);
+
+        verify(mContext).startActivity(InCallPresenter.getInstance().getInCallIntent(false, false));
+        verifyIncomingCallNotificationNotSent();
+    }
+
+    /**
+     * Verifies that for an expected call state change (e.g. NO_CALLS -> PENDING_OUTGOING),
+     * InCallActivity is not displayed.
+     */
+    public void testOnCallListChange_noCallsToPendingOutgoingDoesNotShowUi() {
+        when(mCallList.getPendingOutgoingCall()).thenReturn(TEST_PENDING_CALL);
+        mInCallPresenter.onCallListChange(mCallList);
+
+        verifyInCallActivityNotStarted();
+        verifyIncomingCallNotificationNotSent();
+    }
+
+
+    //TODO
+    public void testCircularReveal_startsCircularRevealForOutgoingCalls() {
+
+    }
+
+    public void testCircularReveal_waitTillCircularRevealSentBeforeShowingCallCard() {
+    }
+
+    public void testHangupOngoingCall_disconnectsCallCorrectly() {
+    }
+
+    public void testAnswerIncomingCall() {
+    }
+
+    public void testDeclineIncomingCall() {
+    }
+
+    private void verifyInCallActivityNotStarted() {
+        verify(mContext, never()).startActivity(Mockito.any(Intent.class));
+    }
+
+    private void verifyIncomingCallNotificationNotSent() {
+        verify(mStatusBarNotifier, never()).updateNotification(Mockito.any(InCallState.class),
+                Mockito.any(CallList.class));
+    }
+}