Merge "Add support for CALL_WAITING supp service notification." am: fc0a5cae29 am: c2390cf0f6
am: 8b96af723e

Change-Id: Ib4db31e2839e8c02f78030ea48f15c96da8f59b7
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2fb1ef7..ff84f29 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1608,7 +1608,6 @@
     <string name="clh_callFailed_protocol_Error_unspecified_txt">Protocol error, unspecified</string>
     <!-- In-call screen: call failure reason (Cause Number 127) -->
     <string name="clh_callFailed_interworking_unspecified_txt">Interworking, unspecified</string>
-
     <!-- Call settings screen, setting option name -->
     <string name="labelCallBarring">Call barring</string>
     <!-- Call barring settings screen, setting summary text when a call barring option is activated -->
@@ -1675,4 +1674,57 @@
     <string name="call_barring_settings">Call barring settings</string>
     <!-- Call barring settings screen, deactivate all call barring settings -->
     <string name="call_barring_deactivate_all_no_password">Deactivate all call barring settings?</string>
+    <!-- Message displayed to the user when an outgoing call is deflected.  This means that the
+         party the user is calling has chosen to send the call to another phone number. -->
+    <string name="supp_service_notification_call_deflected">Call deflected.</string>
+    <!-- Message displayed to the user when an outgoing call is forwarded to another number.
+         This happens because the party the user is calling has call forwarding active. -->
+    <string name="supp_service_notification_call_forwarded">Call forwarded.</string>
+    <!-- Message displayed to the user when an outgoing call is waiting.  This happens when the
+         party the user is calling is already in another call. -->
+    <string name="supp_service_notification_call_waiting">Call is waiting.</string>
+    <!-- Message displayed to the user when they have chosen to block their phone number for an
+         outgoing call, but the network has rejected that request. -->
+    <string name="supp_service_clir_suppression_rejected">Number blocking is rejected.</string>
+    <!-- Message displayed to the user to inform them that the call is to or from a number which is
+         part of a closed user group.  A closed user group is a network feature which restricts
+         calls on a device to members of the closed user group. -->
+    <string name="supp_service_closed_user_group_call">Closed user group call.</string>
+    <!-- Message displayed to the user when incoming call barring is active.  This means that the
+         user has enabled the network feature which prevents all incoming calls. -->
+    <string name="supp_service_incoming_calls_barred">Incoming calls barred.</string>
+    <!-- Message displayed to the user when outgoing call barring is active.  This means that the
+         user has enabled the network feature which prevents all outgoing calls. -->
+    <string name="supp_service_outgoing_calls_barred">Outgoing calls barred.</string>
+    <!-- Message displayed to the user to indicate that call forwarding is active. -->
+    <string name="supp_service_call_forwarding_active">Call forwarding active.</string>
+    <!-- Message displayed to the user when they receive multiple incoming calls at the same time
+         and one of them is forwarded to the network.  Phones can't handle multiple incoming calls
+         so the network will typically forward one of the calls to voicemail or another number
+         defined by the user. -->
+    <string name="supp_service_additional_call_forwarded">Additional call forwarded.</string>
+    <!-- Message displayed to the user to indicate that a call has been successfully transferred
+         to another phone number. -->
+    <string name="supp_service_additional_ect_connected">Explicit call transfer complete.</string>
+    <!-- Message displayed to the user to indicate that the call is in the process of being
+         transferred to another phone number.-->
+    <string name="supp_service_additional_ect_connecting">Explicit call transfer in progress.</string>
+    <!-- Message displayed to the user to indicate that the remote party has put the user
+         on hold. -->
+    <string name="supp_service_call_on_hold">Call on hold.</string>
+    <!-- Message displayed to the user to indicate that the remote party has taken the user
+         off hold. -->
+    <string name="supp_service_call_resumed">Call resumed.</string>
+    <!-- Message displayed to the user to indicate that an incoming call was deflected from another
+         number.  This means that the call originated as a result of the original caller choosing
+         to forward the call to the current user rather than answering it themselves. -->
+    <string name="supp_service_deflected_call">Call was deflected.</string>
+    <!-- Message displayed to the user to indicate that an incoming call was forwarded from another
+         number. -->
+    <string name="supp_service_forwarded_call">Forwarded call.</string>
+    <!-- Message displayed to the user to indicate that they are joining a conference call. -->
+    <string name="supp_service_conference_call">Joining conference call.</string>
+    <!-- Message displayed to the user to indicate that a held call has been released /
+         disconnected. -->
+    <string name="supp_service_held_call_released">Held call has been released.</string>
 </resources>
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 9ed7689..92c3d2c 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -22,6 +22,7 @@
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.telecom.CallAudioState;
@@ -96,7 +97,7 @@
     private static final int MSG_CDMA_VOICE_PRIVACY_OFF = 16;
     private static final int MSG_HANGUP = 17;
 
-    private final Handler mHandler = new Handler() {
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -170,10 +171,7 @@
                                         new ArrayList(Arrays.asList(mSsNotification.history)));
                                 putExtras(lastForwardedNumber);
                             }
-                            if (mSsNotification.code
-                                    == SuppServiceNotification.MO_CODE_CALL_FORWARDED) {
-                                sendConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null);
-                            }
+                            handleSuppServiceNotification(mSsNotification);
                         }
                     }
                     break;
@@ -256,6 +254,115 @@
     };
 
     /**
+     * Handles {@link SuppServiceNotification}s pertinent to Telephony.
+     * @param ssn the notification.
+     */
+    private void handleSuppServiceNotification(SuppServiceNotification ssn) {
+        Log.i(this, "handleSuppServiceNotification: type=%d, code=%d", ssn.notificationType,
+                ssn.code);
+        if (ssn.notificationType == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1
+                && ssn.code == SuppServiceNotification.CODE_1_CALL_FORWARDED) {
+            sendConnectionEvent(TelephonyManager.EVENT_CALL_FORWARDED, null);
+        }
+        sendSuppServiceNotificationEvent(ssn.notificationType, ssn.code);
+    }
+
+    /**
+     * Sends a supplementary service notification connection event.
+     * This connection event includes the type and code, as well as a human readable message which
+     * is suitable for display to the user if the UI chooses to do so.
+     * @param type the {@link SuppServiceNotification#type}.
+     * @param code the {@link SuppServiceNotification#code}.
+     */
+    private void sendSuppServiceNotificationEvent(int type, int code) {
+        Bundle extras = new Bundle();
+        extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_TYPE, type);
+        extras.putInt(TelephonyManager.EXTRA_NOTIFICATION_CODE, code);
+        extras.putCharSequence(TelephonyManager.EXTRA_NOTIFICATION_MESSAGE,
+                getSuppServiceMessage(type, code));
+        sendConnectionEvent(TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION, extras);
+    }
+
+    /**
+     * Retrieves a human-readable message for a supplementary service notification.
+     * This message is suitable for display to the user.
+     * @param type the code group.
+     * @param code the code.
+     * @return A {@link CharSequence} containing the message, or {@code null} if none defined.
+     */
+    private CharSequence getSuppServiceMessage(int type, int code) {
+        int messageId = -1;
+        if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_1) {
+            switch (code) {
+                case SuppServiceNotification.CODE_1_CALL_DEFLECTED:
+                    messageId = R.string.supp_service_notification_call_deflected;
+                    break;
+                case SuppServiceNotification.CODE_1_CALL_FORWARDED:
+                    messageId = R.string.supp_service_notification_call_forwarded;
+                    break;
+                case SuppServiceNotification.CODE_1_CALL_IS_WAITING:
+                    messageId = R.string.supp_service_notification_call_waiting;
+                    break;
+                case SuppServiceNotification.CODE_1_CLIR_SUPPRESSION_REJECTED:
+                    messageId = R.string.supp_service_clir_suppression_rejected;
+                    break;
+                case SuppServiceNotification.CODE_1_CUG_CALL:
+                    messageId = R.string.supp_service_closed_user_group_call;
+                    break;
+                case SuppServiceNotification.CODE_1_INCOMING_CALLS_BARRED:
+                    messageId = R.string.supp_service_incoming_calls_barred;
+                    break;
+                case SuppServiceNotification.CODE_1_OUTGOING_CALLS_BARRED:
+                    messageId = R.string.supp_service_outgoing_calls_barred;
+                    break;
+                case SuppServiceNotification.CODE_1_SOME_CF_ACTIVE:
+                    // Intentional fall through.
+                case SuppServiceNotification.CODE_1_UNCONDITIONAL_CF_ACTIVE:
+                    messageId = R.string.supp_service_call_forwarding_active;
+                    break;
+            }
+        } else if (type == SuppServiceNotification.NOTIFICATION_TYPE_CODE_2) {
+            switch (code) {
+                case SuppServiceNotification.CODE_2_ADDITIONAL_CALL_FORWARDED:
+                    messageId = R.string.supp_service_additional_call_forwarded;
+                    break;
+                case SuppServiceNotification.CODE_2_CALL_CONNECTED_ECT:
+                    messageId = R.string.supp_service_additional_ect_connected;
+                    break;
+                case SuppServiceNotification.CODE_2_CALL_CONNECTING_ECT:
+                    messageId = R.string.supp_service_additional_ect_connecting;
+                    break;
+                case SuppServiceNotification.CODE_2_CALL_ON_HOLD:
+                    messageId = R.string.supp_service_call_on_hold;
+                    break;
+                case SuppServiceNotification.CODE_2_CALL_RETRIEVED:
+                    messageId = R.string.supp_service_call_resumed;
+                    break;
+                case SuppServiceNotification.CODE_2_CUG_CALL:
+                    messageId = R.string.supp_service_closed_user_group_call;
+                    break;
+                case SuppServiceNotification.CODE_2_DEFLECTED_CALL:
+                    messageId = R.string.supp_service_deflected_call;
+                    break;
+                case SuppServiceNotification.CODE_2_FORWARDED_CALL:
+                    messageId = R.string.supp_service_forwarded_call;
+                    break;
+                case SuppServiceNotification.CODE_2_MULTI_PARTY_CALL:
+                    messageId = R.string.supp_service_conference_call;
+                    break;
+                case SuppServiceNotification.CODE_2_ON_HOLD_CALL_RELEASED:
+                    messageId = R.string.supp_service_held_call_released;
+                    break;
+            }
+        }
+        if (messageId != -1 && getPhone() != null && getPhone().getContext() != null) {
+            return getPhone().getContext().getText(messageId);
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise.
      */
     public boolean isCarrierVideoConferencingSupported() {
diff --git a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
index 700b626..3bd2716 100644
--- a/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
+++ b/tests/src/com/android/services/telephony/TelephonyConnectionServiceTest.java
@@ -16,13 +16,29 @@
 
 package com.android.services.telephony;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.test.filters.FlakyTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.telecom.DisconnectCause;
 import android.telecom.TelecomManager;
 import android.telephony.RadioAccessFamily;
 import android.telephony.ServiceState;
-import android.support.test.filters.FlakyTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -30,29 +46,18 @@
 import com.android.internal.telephony.CallStateException;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.gsm.SuppServiceNotification;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 /**
  * Unit tests for TelephonyConnectionService.
  */
@@ -60,6 +65,7 @@
 @RunWith(AndroidJUnit4.class)
 public class TelephonyConnectionServiceTest extends TelephonyTestBase {
 
+    private static final long TIMEOUT_MS = 100;
     private static final int SLOT_0_PHONE_ID = 0;
     private static final int SLOT_1_PHONE_ID = 1;
 
@@ -752,6 +758,58 @@
         }
     }
 
+    @Test
+    @SmallTest
+    public void testSuppServiceNotification() {
+        TestTelephonyConnection c = new TestTelephonyConnection();
+
+        // We need to set the original connection to cause the supp service notification
+        // registration to occur.
+        Phone phone = c.getPhone();
+        c.setOriginalConnection(c.getOriginalConnection());
+
+        // When the registration occurs, we'll capture the handler and message so we can post our
+        // own messages to it.
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> messageCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(phone).registerForSuppServiceNotification(handlerCaptor.capture(),
+                messageCaptor.capture(), any());
+        Handler handler = handlerCaptor.getValue();
+        int message = messageCaptor.getValue();
+
+        // With the handler and message now known, we'll post a supp service notification.
+        AsyncResult result = getSuppServiceNotification(
+                SuppServiceNotification.NOTIFICATION_TYPE_CODE_1,
+                SuppServiceNotification.CODE_1_CALL_FORWARDED);
+        handler.obtainMessage(message, result).sendToTarget();
+        waitForHandlerAction(handler, TIMEOUT_MS);
+
+        assertTrue(c.getLastConnectionEvents().contains(TelephonyManager.EVENT_CALL_FORWARDED));
+
+        // With the handler and message now known, we'll post a supp service notification.
+        result = getSuppServiceNotification(
+                SuppServiceNotification.NOTIFICATION_TYPE_CODE_1,
+                SuppServiceNotification.CODE_1_CALL_IS_WAITING);
+        handler.obtainMessage(message, result).sendToTarget();
+        waitForHandlerAction(handler, TIMEOUT_MS);
+
+        // We we want the 3rd event since the forwarding one above sends 2.
+        assertEquals(c.getLastConnectionEvents().get(2),
+                TelephonyManager.EVENT_SUPPLEMENTARY_SERVICE_NOTIFICATION);
+        Bundle extras = c.getLastConnectionEventExtras().get(2);
+        assertEquals(SuppServiceNotification.NOTIFICATION_TYPE_CODE_1,
+                extras.getInt(TelephonyManager.EXTRA_NOTIFICATION_TYPE));
+        assertEquals(SuppServiceNotification.CODE_1_CALL_IS_WAITING,
+                extras.getInt(TelephonyManager.EXTRA_NOTIFICATION_CODE));
+    }
+
+    private AsyncResult getSuppServiceNotification(int notificationType, int code) {
+        SuppServiceNotification notification = new SuppServiceNotification();
+        notification.notificationType = notificationType;
+        notification.code = code;
+        return new AsyncResult(null, notification, null);
+    }
+
     private Phone makeTestPhone(int phoneId, int serviceState, boolean isEmergencyOnly) {
         Phone phone = mock(Phone.class);
         ServiceState testServiceState = new ServiceState();
diff --git a/tests/src/com/android/services/telephony/TestTelephonyConnection.java b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
index ea0f965..9040257 100644
--- a/tests/src/com/android/services/telephony/TestTelephonyConnection.java
+++ b/tests/src/com/android/services/telephony/TestTelephonyConnection.java
@@ -16,17 +16,25 @@
 
 package com.android.services.telephony;
 
+import android.content.Context;
+import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.Phone;
 
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Mock Telephony Connection used in TelephonyConferenceController.java for testing purpose
  */
@@ -39,8 +47,13 @@
     @Mock
     Call mMockCall;
 
+    @Mock
+    Context mMockContext;
+
     private Phone mMockPhone;
     private int mNotifyPhoneAccountChangedCount = 0;
+    private List<String> mLastConnectionEvents = new ArrayList<>();
+    private List<Bundle> mLastConnectionEventExtras = new ArrayList<>();
 
     @Override
     public com.android.internal.telephony.Connection getOriginalConnection() {
@@ -52,11 +65,17 @@
         MockitoAnnotations.initMocks(this);
 
         mMockPhone = mock(Phone.class);
+        mMockContext = mock(Context.class);
         // Set up mMockRadioConnection and mMockPhone to contain an active call
         when(mMockRadioConnection.getState()).thenReturn(Call.State.ACTIVE);
         when(mMockRadioConnection.getCall()).thenReturn(mMockCall);
+        doNothing().when(mMockRadioConnection).addListener(any(Connection.Listener.class));
+        doNothing().when(mMockRadioConnection).addPostDialListener(
+                any(Connection.PostDialListener.class));
         when(mMockPhone.getRingingCall()).thenReturn(mMockCall);
+        when(mMockPhone.getContext()).thenReturn(null);
         when(mMockCall.getState()).thenReturn(Call.State.ACTIVE);
+        when(mMockCall.getPhone()).thenReturn(mMockPhone);
     }
 
     @Override
@@ -82,7 +101,21 @@
         mNotifyPhoneAccountChangedCount++;
     }
 
+    @Override
+    public void sendConnectionEvent(String event, Bundle extras) {
+        mLastConnectionEvents.add(event);
+        mLastConnectionEventExtras.add(extras);
+    }
+
     public int getNotifyPhoneAccountChangedCount() {
         return mNotifyPhoneAccountChangedCount;
     }
+
+    public List<String> getLastConnectionEvents() {
+        return mLastConnectionEvents;
+    }
+
+    public List<Bundle> getLastConnectionEventExtras() {
+        return mLastConnectionEventExtras;
+    }
 }