Merge "[RCS] The implementation of SubscribePublisher in Android Telephony"
diff --git a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
index 9b0a303..0c91c55 100644
--- a/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
+++ b/src/com/android/services/telephony/rcs/UserCapabilityExchangeImpl.java
@@ -27,12 +27,14 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
@@ -62,6 +64,8 @@
 import com.android.service.ims.presence.SubscribePublisher;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -90,7 +94,6 @@
     private final Context mContext;
     private final UceImplHandler mUceImplHandler;
     private RcsFeatureManager mRcsFeatureManager;
-
     private final PresencePublication mPresencePublication;
     private final PresenceSubscriber mPresenceSubscriber;
 
@@ -100,6 +103,9 @@
     // The callbacks to notify publish state changed.
     private final RemoteCallbackList<IRcsUcePublishStateCallback> mPublishStateCallbacks;
 
+    // The task Ids of pending availability request.
+    private final Set<Integer> mPendingAvailabilityRequests = new HashSet<>();
+
     private final ConcurrentHashMap<Integer, IRcsUceControllerCallback> mPendingCapabilityRequests =
             new ConcurrentHashMap<>();
 
@@ -242,13 +248,15 @@
 
     private void notifyPublishStateChanged(@PresenceBase.PresencePublishState int state) {
         int result = toUcePublishState(state);
-        mPublishStateCallbacks.broadcast(c -> {
-            try {
-                c.onPublishStateChanged(result);
-            } catch (RemoteException e) {
-                logw("notifyPublishStateChanged error: " + e);
-            }
-        });
+        synchronized (mPublishStateCallbacks) {
+            mPublishStateCallbacks.broadcast(c -> {
+                try {
+                    c.onPublishStateChanged(result);
+                } catch (RemoteException e) {
+                    logw("notifyPublishStateChanged error: " + e);
+                }
+            });
+        }
     }
 
     /**
@@ -317,14 +325,88 @@
         if (taskId < 0) {
             try {
                 c.onError(toUceError(taskId));
-                return;
             } catch (RemoteException e) {
                 logi("Calling back to dead service");
             }
+            return;
         }
         mPendingCapabilityRequests.put(taskId, c);
     }
 
+    @Override
+    public int requestCapability(String[] formattedContacts, int taskId) {
+        if (formattedContacts == null || formattedContacts.length == 0) {
+            logw("requestCapability error: contacts is null.");
+            return ResultCode.SUBSCRIBE_INVALID_PARAM;
+        }
+        if (mRcsFeatureManager == null) {
+            logw("requestCapability error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestCapability: taskId=" + taskId);
+
+        try {
+            List<Uri> contactList = Arrays.stream(formattedContacts)
+                    .map(Uri::parse).collect(Collectors.toList());
+            mRcsFeatureManager.requestCapabilities(contactList, taskId);
+        } catch (Exception e) {
+            logw("requestCapability error: " + e.getMessage());
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    @Override
+    public int requestAvailability(String formattedContact, int taskId) {
+        if (formattedContact == null || formattedContact.isEmpty()) {
+            logw("requestAvailability error: contact is null.");
+            return ResultCode.SUBSCRIBE_INVALID_PARAM;
+        }
+        if (mRcsFeatureManager == null) {
+            logw("requestAvailability error: RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        logi("requestAvailability: taskId=" + taskId);
+        addRequestingAvailabilityTaskId(taskId);
+
+        try {
+            Uri contactUri = Uri.parse(formattedContact);
+            List<Uri> contactUris = new ArrayList<>(Arrays.asList(contactUri));
+            mRcsFeatureManager.requestCapabilities(contactUris, taskId);
+        } catch (Exception e) {
+            logw("requestAvailability error: " + e.getMessage());
+            removeRequestingAvailabilityTaskId(taskId);
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+        return ResultCode.SUCCESS;
+    }
+
+    @Override
+    public int getStackStatusForCapabilityRequest() {
+        if (mRcsFeatureManager == null) {
+            logw("Check Stack status: Error! RcsFeatureManager is null.");
+            return ResultCode.ERROR_SERVICE_NOT_AVAILABLE;
+        }
+
+        if (!isCapabilityDiscoveryEnabled(mSubId)) {
+            logw("Check Stack status: Error! capability discovery not enabled");
+            return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+        }
+
+        if (!isEabProvisioned(mContext, mSubId)) {
+            logw("Check Stack status: Error! EAB provisioning disabled.");
+            return ResultCode.ERROR_SERVICE_NOT_ENABLED;
+        }
+
+        if (getPublisherState() != PresenceBase.PUBLISH_STATE_200_OK) {
+            logw("Check Stack status: Error! publish state " + getPublisherState());
+            return ResultCode.ERROR_SERVICE_NOT_PUBLISHED;
+        }
+        return ResultCode.SUCCESS;
+    }
+
     /**
      * The feature callback is to receive the request and update from RcsPresExchangeImplBase
      */
@@ -332,20 +414,42 @@
     public RcsFeatureCallbacks mRcsFeatureCallback = new RcsFeatureCallbacks() {
         public void onCommandUpdate(int commandCode, int operationToken) {
             logi("onCommandUpdate: code=" + commandCode + ", token=" + operationToken);
-            onCommandUpdateForPublishRequest(commandCode, operationToken);
+            if (isPublishRequestExisted(operationToken)) {
+                onCommandUpdateForPublishRequest(commandCode, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                onCommandUpdateForCapabilityRequest(commandCode, operationToken);
+            } else if (isAvailabilityRequestExisted(operationToken)) {
+                onCommandUpdateForAvailabilityRequest(commandCode, operationToken);
+            } else {
+                logw("onCommandUpdate: invalid token " + operationToken);
+            }
         }
 
         /** See {@link RcsPresenceExchangeImplBase#onNetworkResponse(int, String, int)} */
         public void onNetworkResponse(int responseCode, String reason, int operationToken) {
             logi("onNetworkResponse: code=" + responseCode + ", reason=" + reason
                     + ", operationToken=" + operationToken);
-            onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
+            if (isPublishRequestExisted(operationToken)) {
+                onNetworkResponseForPublishRequest(responseCode, reason, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                onNetworkResponseForCapabilityRequest(responseCode, reason, operationToken);
+            } else if (isAvailabilityRequestExisted(operationToken)) {
+                onNetworkResponseForAvailabilityRequest(responseCode, reason, operationToken);
+            } else {
+                logw("onNetworkResponse: invalid token " + operationToken);
+            }
         }
 
         /** See {@link RcsPresenceExchangeImplBase#onCapabilityRequestResponse(List, int)} */
         public void onCapabilityRequestResponsePresence(List<RcsContactUceCapability> infos,
                 int operationToken) {
-
+            if (isAvailabilityRequestExisted(operationToken)) {
+                handleAvailabilityReqResponse(infos, operationToken);
+            } else if (isCapabilityRequestExisted(operationToken)) {
+                handleCapabilityReqResponse(infos, operationToken);
+            } else {
+                logw("capability request response: invalid token " + operationToken);
+            }
         }
 
         /** See {@link RcsPresenceExchangeImplBase#onNotifyUpdateCapabilites(int)} */
@@ -391,7 +495,7 @@
                     uceImpl.onNotifyUpdateCapabilities(publishTriggerType);
                     break;
                 case EVENT_UNPUBLISH:
-                    uceImpl.updatePublisherState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+                    uceImpl.onUnPublish();
                     break;
                 default:
                     Log.w(LOG_TAG, "handleMessage: error=" + msg.what);
@@ -436,6 +540,10 @@
         mPresencePublication.onStackPublishRequested(publishTriggerType);
     }
 
+    private void onUnPublish() {
+        mPresencePublication.setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
+    }
+
     @Override
     public @PresenceBase.PresencePublishState int getPublisherState() {
         return mPublishState;
@@ -471,13 +579,42 @@
         }
         int resultCode = ResultCode.SUCCESS;
         if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
-            logw("Command is failed: taskId=" + operationToken + ", code=" + commandCode);
+            logw("onCommandUpdateForPublishRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
             removePublishRequestTaskId(operationToken);
             resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
         }
         mPresencePublication.onCommandStatusUpdated(operationToken, operationToken, resultCode);
     }
 
+    private void onCommandUpdateForCapabilityRequest(int commandCode, int operationToken) {
+        if (!isCapabilityRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("onCommandUpdateForCapabilityRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
+            mPendingCapabilityRequests.remove(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
+    private void onCommandUpdateForAvailabilityRequest(int commandCode, int operationToken) {
+        if (!isAvailabilityRequestExisted(operationToken)) {
+            return;
+        }
+        int resultCode = ResultCode.SUCCESS;
+        if (commandCode != RcsCapabilityExchange.COMMAND_CODE_SUCCESS) {
+            logw("onCommandUpdateForAvailabilityRequest failed! taskId=" + operationToken
+                    + ", code=" + commandCode);
+            removeRequestingAvailabilityTaskId(operationToken);
+            resultCode = ResultCode.PUBLISH_GENERIC_FAILURE;
+        }
+        mPresenceSubscriber.onCommandStatusUpdated(operationToken, operationToken, resultCode);
+    }
+
     /*
      * Handle the callback method RcsFeatureCallbacks#onNetworkResponse(int, String, int)
      */
@@ -490,19 +627,44 @@
         mPresencePublication.onSipResponse(operationToken, responseCode, reason);
     }
 
-    @Override
-    public int requestCapability(String[] formatedContacts, int taskId) {
-        return 0;
+    private void onNetworkResponseForCapabilityRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isCapabilityRequestExisted(operationToken)) {
+            return;
+        }
+        mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
     }
 
-    @Override
-    public int requestAvailability(String formattedContact, int taskId) {
-        return 0;
+    private void onNetworkResponseForAvailabilityRequest(int responseCode, String reason,
+            int operationToken) {
+        if (!isAvailabilityRequestExisted(operationToken)) {
+            return;
+        }
+        removeRequestingAvailabilityTaskId(operationToken);
+        mPresenceSubscriber.onSipResponse(operationToken, responseCode, reason);
     }
 
-    @Override
-    public int getStackStatusForCapabilityRequest() {
-        return 0;
+    private void handleAvailabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+        try {
+            if (infos == null || infos.isEmpty()) {
+                logw("handle availability request response: infos is null " + token);
+                return;
+            }
+            logi("handleAvailabilityReqResponse: token=" + token);
+            mPresenceSubscriber.updatePresence(infos.get(0));
+        } finally {
+            removeRequestingAvailabilityTaskId(token);
+        }
+    }
+
+    private void handleCapabilityReqResponse(List<RcsContactUceCapability> infos, int token) {
+        if (infos == null) {
+            logw("handleCapabilityReqResponse: infos is null " + token);
+            mPendingCapabilityRequests.remove(token);
+            return;
+        }
+        logi("handleCapabilityReqResponse: token=" + token);
+        mPresenceSubscriber.updatePresences(token, infos, true, null);
     }
 
     @Override
@@ -530,6 +692,28 @@
         }
     }
 
+    private void addRequestingAvailabilityTaskId(int taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            mPendingAvailabilityRequests.contains(taskId);
+        }
+    }
+
+    private void removeRequestingAvailabilityTaskId(int taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            mPendingAvailabilityRequests.remove(taskId);
+        }
+    }
+
+    private boolean isAvailabilityRequestExisted(Integer taskId) {
+        synchronized (mPendingAvailabilityRequests) {
+            return mPendingAvailabilityRequests.contains(taskId);
+        }
+    }
+
+    private boolean isCapabilityRequestExisted(Integer taskId) {
+        return mPendingCapabilityRequests.containsKey(taskId);
+    }
+
     private static String getNumberFromUri(Uri uri) {
         String number = uri.getSchemeSpecificPart();
         String[] numberParts = number.split("[@;:]");
@@ -800,6 +984,45 @@
         }
     };
 
+    private boolean isCapabilityDiscoveryEnabled(int subId) {
+        try {
+            ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+            int discoveryEnabled = manager.getProvisioningIntValue(
+                    ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED);
+            return (discoveryEnabled == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        } catch (Exception e) {
+            logw("isCapabilityDiscoveryEnabled error: " + e.getMessage());
+        }
+        return false;
+    }
+
+    private boolean isEabProvisioned(Context context, int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            logw("isEabProvisioned error: invalid subscriptionId " + subId);
+            return false;
+        }
+
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager != null) {
+            PersistableBundle config = configManager.getConfigForSubId(subId);
+            if (config != null && !config.getBoolean(
+                    CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL)) {
+                return true;
+            }
+        }
+
+        try {
+            ProvisioningManager manager = ProvisioningManager.createForSubscriptionId(subId);
+            int provisioningStatus = manager.getProvisioningIntValue(
+                    ProvisioningManager.KEY_EAB_PROVISIONING_STATUS);
+            return (provisioningStatus == ProvisioningManager.PROVISIONING_VALUE_ENABLED);
+        } catch (Exception e) {
+            logw("isEabProvisioned error: " + e.getMessage());
+        }
+        return false;
+    }
+
     private ImsMmTelManager getImsMmTelManager(int subId) {
         try {
             ImsManager imsManager = (ImsManager) mContext.getSystemService(
diff --git a/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
index 2457d28..82ecd79 100644
--- a/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
+++ b/tests/src/com/android/services/telephony/rcs/UserCapabilityExchangeImplTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.verify;
@@ -33,6 +34,7 @@
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RegistrationManager;
+import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
 import android.telephony.ims.stub.RcsCapabilityExchange;
 import android.telephony.ims.stub.RcsPresenceExchangeImplBase;
@@ -58,6 +60,9 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 @RunWith(AndroidJUnit4.class)
@@ -206,6 +211,35 @@
     }
 
     @Test
+    public void testRequestCapability() throws Exception {
+        int taskId = 1;
+        int sipResponse = 200;
+        List<RcsContactUceCapability> infos = new ArrayList<>();
+        List<Uri> contacts = Arrays.asList(Uri.fromParts("sip", "00000", null));
+        IRcsUceControllerCallback callback = Mockito.mock(IRcsUceControllerCallback.class);
+
+        UserCapabilityExchangeImpl uceImpl = createUserCapabilityExchangeImpl();
+        uceImpl.onRcsConnected(mRcsFeatureManager);
+
+        when(mPresenceSubscriber.requestCapability(anyList(), any())).thenReturn(taskId);
+
+        doAnswer(invocation -> {
+            uceImpl.mRcsFeatureCallback.onCommandUpdate(RcsCapabilityExchange.COMMAND_CODE_SUCCESS,
+                    taskId);
+            uceImpl.mRcsFeatureCallback.onNetworkResponse(sipResponse, null, taskId);
+            uceImpl.mRcsFeatureCallback.onCapabilityRequestResponsePresence(infos, taskId);
+            return null;
+        }).when(mRcsFeatureManager).requestCapabilities(anyList(), anyInt());
+
+        uceImpl.requestCapabilities(contacts, callback);
+        uceImpl.requestCapability(new String[] {"00000"}, taskId);
+
+        verify(mPresenceSubscriber).onCommandStatusUpdated(taskId, taskId, ResultCode.SUCCESS);
+        verify(mPresenceSubscriber).onSipResponse(taskId, sipResponse, null);
+        verify(mPresenceSubscriber).updatePresences(taskId, infos, true, null);
+    }
+
+    @Test
     public void testUpdatePublisherState() throws Exception {
         IRcsUcePublishStateCallback callback = Mockito.mock(IRcsUcePublishStateCallback.class);
         doAnswer(invocation -> {
@@ -235,8 +269,7 @@
         uceImpl.mRcsFeatureCallback.onUnpublish();
         waitForMs(1000);
 
-        assertEquals(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED, uceImpl.getPublisherState());
-        verify(callback).onPublishStateChanged(anyInt());
+        verify(mPresencePublication).setPublishState(PresenceBase.PUBLISH_STATE_NOT_PUBLISHED);
     }
 
     private UserCapabilityExchangeImpl createUserCapabilityExchangeImpl() throws Exception {