From 46d62c03a8066d43a709f52ddf8aa0f57e1ddb2c Mon Sep 17 00:00:00 2001 From: Yorke Lee Date: Mon, 2 Mar 2015 15:15:11 -0800 Subject: 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 --- .../com/android/incallui/AudioModeProvider.java | 2 +- InCallUI/src/com/android/incallui/Call.java | 19 ++- .../src/com/android/incallui/InCallPresenter.java | 26 ++- .../com/android/incallui/InCallServiceImpl.java | 9 +- .../com/android/incallui/InCallPresenterTest.java | 177 +++++++++++++++++++++ 5 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 InCallUI/tests/src/com/android/incallui/InCallPresenterTest.java (limited to 'InCallUI') diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java index c823fda3c..a26766126 100644 --- a/InCallUI/src/com/android/incallui/AudioModeProvider.java +++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java @@ -26,7 +26,7 @@ import java.util.List; /** * 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 663c4120a..16e4a2c76 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 @@ public final class Call { 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 final class Call { } public int getState() { - if (mTelecommCall.getParent() != null) { + if (mTelecommCall != null && mTelecommCall.getParent() != null) { return State.CONFERENCED; } else { return mState; @@ -401,6 +412,12 @@ public final class Call { @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 0343ed6ca..e90dc99a5 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -37,6 +37,7 @@ import android.view.View; 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 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener, return sInCallPresenter; } + @NeededForTesting + static synchronized void setInstance(InCallPresenter inCallPresenter) { + sInCallPresenter = inCallPresenter; + } + @Override public void setPhone(Phone phone) { mPhone = phone; @@ -207,7 +213,12 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener, 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 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener, 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 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener, // [ 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 @@ public class InCallPresenter implements CallList.Listener, InCallPhoneListener, 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 17f4e174d..adb069784 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 @@ public class InCallServiceImpl extends InCallService { @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 000000000..6d6087615 --- /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)); + } +} -- cgit v1.2.3