Merge "DO NOT MERGE Adding unit tests for ExternalCallList." into nyc-mr1-dev
am: ff81bf19ea

* commit 'ff81bf19ea85ae61cc2904a5cb79d8c6c61f31f5':
  DO NOT MERGE Adding unit tests for ExternalCallList.

Change-Id: Id8571be3c7d1a1beb0e7d072fe511875a878b690
diff --git a/InCallUI/tests/src/com/android/incallui/ExternalCallListTest.java b/InCallUI/tests/src/com/android/incallui/ExternalCallListTest.java
new file mode 100644
index 0000000..070bdf5
--- /dev/null
+++ b/InCallUI/tests/src/com/android/incallui/ExternalCallListTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 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 android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.*;
+import android.telecom.Call;
+import android.test.AndroidTestCase;
+
+import java.lang.reflect.Constructor;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class ExternalCallListTest extends AndroidTestCase {
+
+    private static class Listener implements ExternalCallList.ExternalCallListener {
+        private CountDownLatch mCallAddedLatch = new CountDownLatch(1);
+        private CountDownLatch mCallRemovedLatch = new CountDownLatch(1);
+        private CountDownLatch mCallUpdatedLatch = new CountDownLatch(1);
+
+        @Override
+        public void onExternalCallAdded(Call call) {
+            mCallAddedLatch.countDown();
+        }
+
+        @Override
+        public void onExternalCallRemoved(Call call) {
+            mCallRemovedLatch.countDown();
+        }
+
+        @Override
+        public void onExternalCallUpdated(Call call) {
+            mCallUpdatedLatch.countDown();
+        }
+
+        public boolean awaitCallAdded() {
+            try {
+                return mCallAddedLatch.await(WAIT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitCallRemoved() {
+            try {
+                return mCallRemovedLatch.await(WAIT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitCallUpdated() {
+            try {
+                return mCallUpdatedLatch.await(WAIT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+
+    private static final int WAIT_TIMEOUT_MILLIS = 5000;
+
+    private ExternalCallList mExternalCallList = new ExternalCallList();
+    private Listener mExternalCallListener = new Listener();
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mExternalCallList.addExternalCallListener(mExternalCallListener);
+    }
+
+    public void testAddCallSuccess() {
+        TestTelecomCall call = getTestCall(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+        mExternalCallList.onCallAdded(call.getCall());
+        assertTrue(mExternalCallListener.awaitCallAdded());
+    }
+
+    public void testAddCallFail() {
+        TestTelecomCall call = getTestCall(0 /* no properties */);
+        try {
+            mExternalCallList.onCallAdded(call.getCall());
+            fail();
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    public void testUpdateCall() {
+        TestTelecomCall call = getTestCall(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+        mExternalCallList.onCallAdded(call.getCall());
+        assertTrue(mExternalCallListener.awaitCallAdded());
+
+        call.forceDetailsUpdate();
+        assertTrue(mExternalCallListener.awaitCallUpdated());
+    }
+
+    public void testRemoveCall() {
+        TestTelecomCall call = getTestCall(Call.Details.PROPERTY_IS_EXTERNAL_CALL);
+        mExternalCallList.onCallAdded(call.getCall());
+        assertTrue(mExternalCallListener.awaitCallAdded());
+
+        mExternalCallList.onCallRemoved(call.getCall());
+        assertTrue(mExternalCallListener.awaitCallRemoved());
+    }
+
+    private TestTelecomCall getTestCall(int properties) {
+        TestTelecomCall testCall = TestTelecomCall.createInstance(
+                "1",
+                Uri.parse("tel:650-555-1212"), /* handle */
+                TelecomManager.PRESENTATION_ALLOWED, /* handlePresentation */
+                "Joe", /* callerDisplayName */
+                TelecomManager.PRESENTATION_ALLOWED, /* callerDisplayNamePresentation */
+                new PhoneAccountHandle(new ComponentName("test", "class"),
+                        "handle"), /* accountHandle */
+                Call.Details.CAPABILITY_CAN_PULL_CALL, /* capabilities */
+                properties, /* properties */
+                null, /* disconnectCause */
+                0, /* connectTimeMillis */
+                null, /* GatewayInfo */
+                VideoProfile.STATE_AUDIO_ONLY, /* videoState */
+                null, /* statusHints */
+                null, /* extras */
+                null /* intentExtras */);
+        return testCall;
+    }
+}
diff --git a/InCallUI/tests/src/com/android/incallui/ExternalCallNotifierTest.java b/InCallUI/tests/src/com/android/incallui/ExternalCallNotifierTest.java
new file mode 100644
index 0000000..e57efef
--- /dev/null
+++ b/InCallUI/tests/src/com/android/incallui/ExternalCallNotifierTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 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.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.contacts.common.preference.ContactsPreferences;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.telecom.*;
+import android.telecom.Call;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContext;
+
+/**
+ * Unit tests for {@link ExternalCallNotifier}.
+ */
+public class ExternalCallNotifierTest extends AndroidTestCase {
+    private static final int TIMEOUT_MILLIS = 5000;
+    private static final String NAME_PRIMARY = "Full Name";
+    private static final String NAME_ALTERNATIVE = "Name, Full";
+    private static final String LOCATION = "US";
+    private static final String NUMBER = "6505551212";
+
+    @Mock private ContactsPreferences mContactsPreferences;
+    @Mock private NotificationManager mNotificationManager;
+    @Mock private MockContext mMockContext;
+    @Mock private Resources mResources;
+    @Mock private StatusBarNotifier mStatusBarNotifier;
+    @Mock private ContactInfoCache mContactInfoCache;
+    @Mock private TelecomManager mTelecomManager;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private ProximitySensor mProximitySensor;
+    @Mock private CallList mCallList;
+    private InCallPresenter mInCallPresenter;
+    private ExternalCallNotifier mExternalCallNotifier;
+    private ContactInfoCache.ContactCacheEntry mContactInfo;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+
+        when(mContactsPreferences.getDisplayOrder())
+                .thenReturn(ContactsPreferences.DISPLAY_ORDER_PRIMARY);
+
+        // Setup the mock context to return mocks for some of the needed services; the notification
+        // service is especially important as we want to be able to intercept calls into it and
+        // validate the notifcations.
+        when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
+                .thenReturn(mNotificationManager);
+        when(mMockContext.getSystemService(eq(Context.TELECOM_SERVICE)))
+                .thenReturn(mTelecomManager);
+        when(mMockContext.getSystemService(eq(Context.TELEPHONY_SERVICE)))
+                .thenReturn(mTelephonyManager);
+
+        // These aspects of the context are used by the notification builder to build the actual
+        // notification; we will rely on the actual implementations of these.
+        when(mMockContext.getPackageManager()).thenReturn(mContext.getPackageManager());
+        when(mMockContext.getResources()).thenReturn(mContext.getResources());
+        when(mMockContext.getApplicationInfo()).thenReturn(mContext.getApplicationInfo());
+        when(mMockContext.getContentResolver()).thenReturn(mContext.getContentResolver());
+        when(mMockContext.getPackageName()).thenReturn(mContext.getPackageName());
+
+        ContactsPreferencesFactory.setTestInstance(null);
+        mExternalCallNotifier = new ExternalCallNotifier(mMockContext, mContactInfoCache);
+
+        // We don't directly use the InCallPresenter in the test, or even in ExternalCallNotifier
+        // itself.  However, ExternalCallNotifier needs to make instances of
+        // com.android.incallui.Call for the purpose of performing contact cache lookups.  The
+        // Call class depends on the static InCallPresenter for a number of things, so we need to
+        // set it up here to prevent crashes.
+        mInCallPresenter = InCallPresenter.getInstance();
+        mInCallPresenter.setUp(mMockContext, mCallList, new ExternalCallList(),
+                null, mStatusBarNotifier, mExternalCallNotifier, mContactInfoCache,
+                mProximitySensor);
+
+        // Unlocked all contact info is available
+        mContactInfo = new ContactInfoCache.ContactCacheEntry();
+        mContactInfo.namePrimary = NAME_PRIMARY;
+        mContactInfo.nameAlternative = NAME_ALTERNATIVE;
+        mContactInfo.location = LOCATION;
+        mContactInfo.number = NUMBER;
+
+        // Given the mock ContactInfoCache cache, we need to mock out what happens when the
+        // ExternalCallNotifier calls into the contact info cache to do a lookup.  We will always
+        // return mock info stored in mContactInfo.
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                Object[] args = invocation.getArguments();
+                com.android.incallui.Call call = (com.android.incallui.Call) args[0];
+                ContactInfoCache.ContactInfoCacheCallback callback
+                        = (ContactInfoCache.ContactInfoCacheCallback) args[2];
+                callback.onContactInfoComplete(call.getId(), mContactInfo);
+                return null;
+            }
+        }).when(mContactInfoCache).findInfo(any(com.android.incallui.Call.class), anyBoolean(),
+                any(ContactInfoCache.ContactInfoCacheCallback.class));
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        ContactsPreferencesFactory.setTestInstance(null);
+        mInCallPresenter.tearDown();
+    }
+
+    public void testPostNonPullable() {
+        TestTelecomCall call = getTestCall(false);
+        mExternalCallNotifier.onExternalCallAdded(call.getCall());
+        Notification notification = verifyNotificationPosted();
+        assertNull(notification.actions);
+    }
+
+    public void testPostPullable() {
+        TestTelecomCall call = getTestCall(true);
+        mExternalCallNotifier.onExternalCallAdded(call.getCall());
+        Notification notification = verifyNotificationPosted();
+        assertEquals(1, notification.actions.length);
+    }
+
+    public void testNotificationDismissed() {
+        TestTelecomCall call = getTestCall(false);
+        mExternalCallNotifier.onExternalCallAdded(call.getCall());
+        verifyNotificationPosted();
+
+        mExternalCallNotifier.onExternalCallRemoved(call.getCall());
+        verify(mNotificationManager, timeout(TIMEOUT_MILLIS)).cancel(eq("EXTERNAL_CALL"), eq(0));
+    }
+
+    public void testNotificationUpdated() {
+        TestTelecomCall call = getTestCall(false);
+        mExternalCallNotifier.onExternalCallAdded(call.getCall());
+        verifyNotificationPosted();
+
+        call.setCapabilities(android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL);
+        mExternalCallNotifier.onExternalCallUpdated(call.getCall());
+
+        ArgumentCaptor<Notification> notificationCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager, timeout(TIMEOUT_MILLIS).times(2))
+                .notify(eq("EXTERNAL_CALL"), eq(0), notificationCaptor.capture());
+        Notification notification1 = notificationCaptor.getAllValues().get(0);
+        assertNull(notification1.actions);
+        Notification notification2 = notificationCaptor.getAllValues().get(1);
+        assertEquals(1, notification2.actions.length);
+    }
+
+    private Notification verifyNotificationPosted() {
+        ArgumentCaptor<Notification> notificationCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager, timeout(TIMEOUT_MILLIS))
+                .notify(eq("EXTERNAL_CALL"), eq(0), notificationCaptor.capture());
+        return notificationCaptor.getValue();
+    }
+
+    private TestTelecomCall getTestCall(boolean canPull) {
+        TestTelecomCall testCall = TestTelecomCall.createInstance(
+                "1",
+                Uri.parse("tel:650-555-1212"), /* handle */
+                TelecomManager.PRESENTATION_ALLOWED, /* handlePresentation */
+                "Joe", /* callerDisplayName */
+                TelecomManager.PRESENTATION_ALLOWED, /* callerDisplayNamePresentation */
+                new PhoneAccountHandle(new ComponentName("test", "class"),
+                        "handle"), /* accountHandle */
+                canPull ? android.telecom.Call.Details.CAPABILITY_CAN_PULL_CALL : 0, /* capabilities */
+                Call.Details.PROPERTY_IS_EXTERNAL_CALL, /* properties */
+                null, /* disconnectCause */
+                0, /* connectTimeMillis */
+                null, /* GatewayInfo */
+                VideoProfile.STATE_AUDIO_ONLY, /* videoState */
+                null, /* statusHints */
+                null, /* extras */
+                null /* intentExtras */);
+        return testCall;
+    }
+}
diff --git a/InCallUI/tests/src/com/android/incallui/TestTelecomCall.java b/InCallUI/tests/src/com/android/incallui/TestTelecomCall.java
new file mode 100644
index 0000000..48ac6e1
--- /dev/null
+++ b/InCallUI/tests/src/com/android/incallui/TestTelecomCall.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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 com.google.common.base.Preconditions;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.telecom.DisconnectCause;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Wrapper class which uses reflection to create instances of {@link android.telecom.Call} for use
+ * with unit testing.  Since {@link android.telecom.Call} is final, it cannot be mocked using
+ * mockito, and since all setter methods are hidden, it is necessary to use reflection.  In the
+ * future, it would be desirable to replace this if a different mocking solution is used.
+ */
+public class TestTelecomCall {
+
+    private android.telecom.Call mCall;
+
+    public static @Nullable TestTelecomCall createInstance(String callId,
+            Uri handle,
+            int handlePresentation,
+            String callerDisplayName,
+            int callerDisplayNamePresentation,
+            PhoneAccountHandle accountHandle,
+            int capabilities,
+            int properties,
+            DisconnectCause disconnectCause,
+            long connectTimeMillis,
+            GatewayInfo gatewayInfo,
+            int videoState,
+            StatusHints statusHints,
+            Bundle extras,
+            Bundle intentExtras) {
+
+        try {
+            // Phone and InCall adapter are @hide, so we cannot refer to them directly.
+            Class<?> phoneClass = Class.forName("android.telecom.Phone");
+            Class<?> incallAdapterClass = Class.forName("android.telecom.InCallAdapter");
+            Class<?> callClass = android.telecom.Call.class;
+            Constructor<?> cons = callClass
+                    .getDeclaredConstructor(phoneClass, String.class, incallAdapterClass);
+            cons.setAccessible(true);
+
+            android.telecom.Call call = (android.telecom.Call) cons.newInstance(null, callId, null);
+
+            // Create an instance of the call details.
+            Class<?> callDetailsClass = android.telecom.Call.Details.class;
+            Constructor<?> detailsCons = callDetailsClass.getDeclaredConstructor(
+                    String.class, /* telecomCallId */
+                    Uri.class, /* handle */
+                    int.class, /* handlePresentation */
+                    String.class, /* callerDisplayName */
+                    int.class, /* callerDisplayNamePresentation */
+                    PhoneAccountHandle.class, /* accountHandle */
+                    int.class, /* capabilities */
+                    int.class, /* properties */
+                    DisconnectCause.class, /* disconnectCause */
+                    long.class, /* connectTimeMillis */
+                    GatewayInfo.class, /* gatewayInfo */
+                    int.class, /* videoState */
+                    StatusHints.class, /* statusHints */
+                    Bundle.class, /* extras */
+                    Bundle.class /* intentExtras */);
+            detailsCons.setAccessible(true);
+
+            android.telecom.Call.Details details = (android.telecom.Call.Details)
+                    detailsCons.newInstance(callId, handle, handlePresentation, callerDisplayName,
+                            callerDisplayNamePresentation, accountHandle, capabilities, properties,
+                            disconnectCause, connectTimeMillis, gatewayInfo, videoState,
+                            statusHints,
+                            extras, intentExtras);
+
+            // Finally, set this as the details of the call.
+            Field detailsField = call.getClass().getDeclaredField("mDetails");
+            detailsField.setAccessible(true);
+            detailsField.set(call, details);
+
+            return new TestTelecomCall(call);
+        } catch (NoSuchMethodException nsm) {
+            return null;
+        } catch (ClassNotFoundException cnf) {
+            return null;
+        } catch (IllegalAccessException e) {
+            return null;
+        } catch (InstantiationException e) {
+            return null;
+        } catch (InvocationTargetException e) {
+            return null;
+        } catch (NoSuchFieldException e) {
+            return null;
+        }
+    }
+
+    private TestTelecomCall(android.telecom.Call call) {
+        mCall = call;
+    }
+
+    public android.telecom.Call getCall() {
+        return mCall;
+    }
+
+    public void forceDetailsUpdate() {
+        Preconditions.checkNotNull(mCall);
+
+        try {
+            Method method = mCall.getClass().getDeclaredMethod("fireDetailsChanged",
+                    android.telecom.Call.Details.class);
+            method.setAccessible(true);
+            method.invoke(mCall, mCall.getDetails());
+        } catch (NoSuchMethodException e) {
+            Log.e(this, "forceDetailsUpdate", e);
+        } catch (InvocationTargetException e) {
+            Log.e(this, "forceDetailsUpdate", e);
+        } catch (IllegalAccessException e) {
+            Log.e(this, "forceDetailsUpdate", e);
+        }
+    }
+
+    public void setCapabilities(int capabilities) {
+        Preconditions.checkNotNull(mCall);
+        try {
+            Field field = mCall.getDetails().getClass().getDeclaredField("mCallCapabilities");
+            field.setAccessible(true);
+            field.set(mCall.getDetails(), capabilities);
+        } catch (IllegalAccessException e) {
+            Log.e(this, "setProperties", e);
+        } catch (NoSuchFieldException e) {
+            Log.e(this, "setProperties", e);
+        }
+    }
+
+    public void setCall(android.telecom.Call call) {
+        mCall = call;
+    }
+}