aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Lin <jamescflin@google.com>2020-10-30 00:57:31 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2020-10-30 00:57:31 +0000
commit483099fd62629f35e61abfdf2de73f89dfde09f8 (patch)
tree1328c8f1f1796379e6db97a6c0f8a57ee8d5f9af
parent7a1723d1dfe05a353a627f23ba5c2913214dd5f2 (diff)
parent25195604ac1852c8dff0ca22f1c9000b7f1f6477 (diff)
downloadims-483099fd62629f35e61abfdf2de73f89dfde09f8.tar.gz
Merge "[RCS UCE] Implement the device capabilities publishing."
-rw-r--r--src/java/com/android/ims/RcsFeatureConnection.java7
-rw-r--r--src/java/com/android/ims/RcsFeatureManager.java6
-rw-r--r--src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java21
-rw-r--r--src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java81
-rw-r--r--src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java374
-rw-r--r--src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java159
-rw-r--r--src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java159
-rw-r--r--src/java/com/android/ims/rcs/uce/util/NetworkSipCode.java8
-rw-r--r--src/java/com/android/ims/rcs/uce/util/UceUtils.java64
-rw-r--r--tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java2
-rw-r--r--tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java2
-rw-r--r--tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java261
12 files changed, 1134 insertions, 10 deletions
diff --git a/src/java/com/android/ims/RcsFeatureConnection.java b/src/java/com/android/ims/RcsFeatureConnection.java
index a7be0d41..6768f5aa 100644
--- a/src/java/com/android/ims/RcsFeatureConnection.java
+++ b/src/java/com/android/ims/RcsFeatureConnection.java
@@ -33,8 +33,10 @@ import android.telephony.ims.aidl.ISipTransport;
import android.telephony.ims.feature.CapabilityChangeRequest;
import android.telephony.ims.feature.ImsFeature;
+import com.android.ims.rcs.uce.RcsCapabilityExchangeImplAdapter.PublishResponseCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.telephony.Rlog;
+
import java.util.List;
/**
@@ -223,6 +225,11 @@ public class RcsFeatureConnection extends FeatureConnection {
}
}
+ public void requestPublication(String pidfXml, PublishResponseCallback responseCallback)
+ throws RemoteException {
+ // TODO: add the new API: requestPublication
+ }
+
public void requestCapabilities(List<Uri> uris, int taskId) throws RemoteException {
synchronized (mLock) {
checkServiceIsReady();
diff --git a/src/java/com/android/ims/RcsFeatureManager.java b/src/java/com/android/ims/RcsFeatureManager.java
index 4a5a16b9..7ec8f374 100644
--- a/src/java/com/android/ims/RcsFeatureManager.java
+++ b/src/java/com/android/ims/RcsFeatureManager.java
@@ -47,6 +47,7 @@ import android.telephony.ims.stub.RcsSipOptionsImplBase;
import android.util.Log;
import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.ims.rcs.uce.RcsCapabilityExchangeImplAdapter.PublishResponseCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.ITelephony;
import com.android.telephony.Rlog;
@@ -449,6 +450,11 @@ public class RcsFeatureManager implements FeatureUpdates {
mRcsFeatureConnection.requestPublication(capabilities, taskId);
}
+ public void requestPublication(String pidfXml, PublishResponseCallback responseCallback)
+ throws RemoteException {
+ mRcsFeatureConnection.requestPublication(pidfXml, responseCallback);
+ }
+
public void requestCapabilities(List<Uri> uris, int taskId) throws RemoteException {
mRcsFeatureConnection.requestCapabilities(uris, taskId);
}
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
index 92781218..75c76281 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishController.java
@@ -95,6 +95,27 @@ public interface PublishController extends ControllerBase {
void requestPublishFromInternal(@PublishTriggerType int type, long delay);
/**
+ * Receive the command error callback of the request from ImsService.
+ */
+ void onRequestCommandError(PublishRequestResponse requestResponse);
+
+ /**
+ * Receive the network response callback fo the request from ImsService.
+ */
+ void onRequestNetworkResp(PublishRequestResponse requestResponse);
+
+ /**
+ * Set the timer to cancel the request. This timer is to prevent taking too long for
+ * waiting the response callback.
+ */
+ void setupRequestCanceledTimer(long taskId, long delay);
+
+ /**
+ * Clear the request canceled timer. This api will be called if the request is finished.
+ */
+ void clearRequestCanceledTimer();
+
+ /**
* Update the publish request result.
*/
void updatePublishRequestResult(int publishState);
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
index df7621f9..f185417e 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishControllerImpl.java
@@ -213,6 +213,30 @@ public class PublishControllerImpl implements PublishController {
}
@Override
+ public void onRequestCommandError(PublishRequestResponse requestResponse) {
+ logd("onRequestCommandError: taskId=" + requestResponse.getTaskId());
+ mPublishHandler.onRequestCommandError(requestResponse);
+ }
+
+ @Override
+ public void onRequestNetworkResp(PublishRequestResponse requestResponse) {
+ logd("onRequestNetworkResp: taskId=" + requestResponse.getTaskId());
+ mPublishHandler.onRequestNetworkResponse(requestResponse);
+ }
+
+ @Override
+ public void setupRequestCanceledTimer(long taskId, long delay) {
+ logd("setupRequestCanceledTimer: taskId=" + taskId + ", delay=" + delay);
+ mPublishHandler.setRequestCanceledTimer(taskId, delay);
+ }
+
+ @Override
+ public void clearRequestCanceledTimer() {
+ logd("clearRequestCanceledTimer");
+ mPublishHandler.clearRequestCanceledTimer();
+ }
+
+ @Override
public void updatePublishRequestResult(@PublishState int publishState) {
logd("updatePublishRequestResult: " + publishState);
mPublishHandler.onPublishStateChanged(publishState);
@@ -231,6 +255,9 @@ public class PublishControllerImpl implements PublishController {
private class PublishHandler extends Handler {
private static final int MSG_PUBLISH_STATE_CHANGED = 1;
private static final int MSG_REQUEST_PUBLISH = 2;
+ private static final int MSG_REQUEST_CMD_ERROR = 3;
+ private static final int MSG_REQUEST_SIP_RESPONSE = 4;
+ private static final int MSG_REQUEST_CANCELED = 5;
public PublishHandler(Looper looper) {
super(looper);
@@ -250,6 +277,21 @@ public class PublishControllerImpl implements PublishController {
int type = (Integer) message.obj;
handleRequestPublishMessage(type);
break;
+
+ case MSG_REQUEST_CMD_ERROR:
+ PublishRequestResponse cmdErrorResponse = (PublishRequestResponse) message.obj;
+ mPublishProcessor.onCommandError(cmdErrorResponse);
+ break;
+
+ case MSG_REQUEST_SIP_RESPONSE:
+ PublishRequestResponse networkResponse = (PublishRequestResponse) message.obj;
+ mPublishProcessor.onNetworkResponse(networkResponse);
+ break;
+
+ case MSG_REQUEST_CANCELED:
+ long taskId = (Long) message.obj;
+ handleRequestCanceledMessage(taskId);
+ break;
}
}
@@ -297,6 +339,39 @@ public class PublishControllerImpl implements PublishController {
sendMessage(message);
}
}
+
+ public void onRequestCommandError(PublishRequestResponse requestResponse) {
+ if (mIsDestroyedFlag) return;
+
+ Message message = obtainMessage();
+ message.what = MSG_REQUEST_CMD_ERROR;
+ message.obj = requestResponse;
+ sendMessage(message);
+ }
+
+ public void onRequestNetworkResponse(PublishRequestResponse requestResponse) {
+ if (mIsDestroyedFlag) return;
+
+ Message message = obtainMessage();
+ message.what = MSG_REQUEST_SIP_RESPONSE;
+ message.obj = requestResponse;
+ sendMessage(message);
+ }
+
+ public void setRequestCanceledTimer(long taskId, long delay) {
+ if (mIsDestroyedFlag) return;
+ removeMessages(MSG_REQUEST_CANCELED, (Long) taskId);
+
+ Message message = obtainMessage();
+ message.what = MSG_REQUEST_CANCELED;
+ message.obj = (Long) taskId;
+ sendMessageDelayed(message, delay);
+ }
+
+ public void clearRequestCanceledTimer() {
+ if (mIsDestroyedFlag) return;
+ removeMessages(MSG_REQUEST_CANCELED);
+ }
}
/**
@@ -325,10 +400,14 @@ public class PublishControllerImpl implements PublishController {
public void handleRequestPublishMessage(@PublishTriggerType int type) {
if (mIsDestroyedFlag) return;
- logd("handleRequestPublishMessage");
mPublishProcessor.doPublish(type);
}
+ public void handleRequestCanceledMessage(long taskId) {
+ if (mIsDestroyedFlag) return;
+ mPublishProcessor.cancelPublishRequest(taskId);
+ }
+
@VisibleForTesting
public void setPublishStateCallback(RemoteCallbackList<IRcsUcePublishStateCallback> list) {
mPublishStateCallbacks = list;
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
index 76231254..64a31394 100644
--- a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessor.java
@@ -17,27 +17,81 @@
package com.android.ims.rcs.uce.presence.publish;
import android.content.Context;
+import android.os.RemoteException;
+import android.telephony.ims.RcsContactUceCapability;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.presence.pidfparser.PidfParser;
import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType;
+import com.android.ims.rcs.uce.util.UceUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
+
+/**
+ * Send the publish request and handle the response of the publish request result.
+ */
public class PublishProcessor {
+ private static final String LOG_TAG = "PublishProcessor";
+
+ // The length of time waiting for the response callback.
+ private static final long RESPONSE_CALLBACK_WAITING_TIME = 60000L;
+
+ private final int mSubId;
+ private final Context mContext;
+ private volatile boolean mIsDestroyed;
+ private volatile RcsFeatureManager mRcsFeatureManager;
+
+ // Manage the state of the publish processor.
+ private PublishProcessorState mProcessorState;
+
+ // The information of the device's capabilities.
+ private DeviceCapabilityInfo mDeviceCapabilities;
+
+ // The callback of the PublishController
+ private PublishControllerCallback mPublishCtrlCallback;
+
+ private final LocalLog mLocalLog = new LocalLog(20);
+
public PublishProcessor(Context context, int subId, DeviceCapabilityInfo capabilityInfo,
- PublishControllerCallback callback) {
+ PublishControllerCallback publishCtrlCallback) {
+ mSubId = subId;
+ mContext = context;
+ mDeviceCapabilities = capabilityInfo;
+ mPublishCtrlCallback = publishCtrlCallback;
+ mProcessorState = new PublishProcessorState();
}
+ /**
+ * The RcsFeature has been connected to the framework.
+ */
public void onRcsConnected(RcsFeatureManager featureManager) {
- // TODO: Implement this method
+ mLocalLog.log("onRcsConnected");
+ logi("onRcsConnected");
+ mRcsFeatureManager = featureManager;
}
+ /**
+ * The framework has lost the binding to the RcsFeature.
+ */
public void onRcsDisconnected() {
- // TODO: Implement this method
+ mLocalLog.log("onRcsDisconnected");
+ logi("onRcsDisconnected");
+ mRcsFeatureManager = null;
}
+ /**
+ * Set the destroy flag
+ */
public void onDestroy() {
- // TODO: Implement this method
+ mLocalLog.log("onDestroy");
+ logi("onDestroy");
+ mIsDestroyed = true;
}
/**
@@ -45,6 +99,316 @@ public class PublishProcessor {
* @param triggerType The type of triggering the publish request.
*/
public void doPublish(@PublishTriggerType int triggerType) {
- // TODO: Implement this method
+ if (mIsDestroyed) return;
+
+ mLocalLog.log("doPublish: trigger type=" + triggerType);
+ logi("doPublish: trigger type=" + triggerType);
+
+ // Check if it should reset the retry count.
+ if (isResetRetryNeeded(triggerType)) {
+ mProcessorState.resetRetryCount();
+ }
+
+ // Return if this request is not allowed to execute.
+ if (!isRequestAllowed()) {
+ mLocalLog.log("doPublish: The request is not allowed");
+ return;
+ }
+
+ // Get the latest device's capabilities.
+ RcsContactUceCapability deviceCapability =
+ mDeviceCapabilities.getDeviceCapabilities(mContext);
+ if (deviceCapability == null) {
+ logw("doPublish: device capability is null");
+ return;
+ }
+
+ // Convert the device's capabilities to pidf format.
+ String pidfXml = PidfParser.convertToPidf(deviceCapability);
+ if (TextUtils.isEmpty(pidfXml)) {
+ logw("doPublish: pidfXml is empty");
+ return;
+ }
+
+ // Publish to the Presence server.
+ publishCapabilities(pidfXml);
+ }
+
+ // Check if the giving trigger type should reset the retry count.
+ private boolean isResetRetryNeeded(@PublishTriggerType int triggerType) {
+ // Do no reset the retry count if the request is triggered by the previous failed retry.
+ if (triggerType == PublishController.PUBLISH_TRIGGER_RETRY) {
+ return false;
+ }
+ return true;
+ }
+
+ // Check if the publish request is allowed to execute.
+ private boolean isRequestAllowed() {
+ // Check if the instance is destroyed.
+ if (mIsDestroyed) {
+ logd("isPublishAllowed: This instance is already destroyed");
+ return false;
+ }
+
+ // Check if it has provisioned. When the provisioning changes, a new publish request will
+ // be triggered.
+ if (!UceUtils.isEabProvisioned(mContext, mSubId)) {
+ logd("isPublishAllowed: NOT provisioned");
+ return false;
+ }
+
+ // Do not request publish if the IMS is not registered. When the IMS is registered
+ // afterward, a new publish request will be triggered.
+ if (!mDeviceCapabilities.isImsRegistered()) {
+ logd("isPublishAllowed: IMS is not registered");
+ return false;
+ }
+
+ // Set the pending flag if there's already a request running now.
+ if (mProcessorState.isPublishingNow()) {
+ logd("isPublishAllowed: There is already a request running now");
+ mProcessorState.setPendingRequest(true);
+ return false;
+ }
+
+ // Check if the publishing request has reached the maximum number of retries.
+ if (mProcessorState.isReachMaximumRetries()) {
+ logd("isPublishAllowed: It has reached maximum number of retries");
+ return false;
+ }
+
+ // Skip this request and re-send the request with the delay time if the publish request
+ // executes too frequently.
+ if (!mProcessorState.isCurrentTimeAllowed()) {
+ logd("isPublishAllowed: Current time is not allowed, resend this request");
+ long delayTime = mProcessorState.getDelayTimeToAllowPublish();
+ mPublishCtrlCallback.requestPublishFromInternal(
+ PublishController.PUBLISH_TRIGGER_RETRY, delayTime);
+ return false;
+ }
+ return true;
+ }
+
+ // Publish the device capabilities with the given pidf
+ private void publishCapabilities(String pidfXml) {
+ if (mIsDestroyed) {
+ logw("publishCapabilities: This instance is already destroyed");
+ return;
+ }
+
+ // Check if the RCS is connected.
+ RcsFeatureManager featureManager = mRcsFeatureManager;
+ if (featureManager == null) {
+ mLocalLog.log("publish capabilities: not connected");
+ logw("publishCapabilities: NOT connected");
+ return;
+ }
+
+ PublishRequestResponse requestResponse = null;
+ try {
+ // Set publishing flag
+ mProcessorState.setPublishingFlag(true);
+
+ // Clear the pending request flag since we're publishing the latest device's capability
+ mProcessorState.setPendingRequest(false);
+
+ // Generate a unique taskId to track this request.
+ long taskId = mProcessorState.generatePublishTaskId();
+ requestResponse = new PublishRequestResponse(mPublishCtrlCallback, taskId);
+
+ mLocalLog.log("publish capabilities: taskId=" + taskId);
+ logi("publishCapabilities: taskId=" + taskId);
+
+ // request publication
+ featureManager.requestPublication(pidfXml, requestResponse);
+
+ // Send a request canceled timer to avoid waiting too long for the response callback.
+ mPublishCtrlCallback.setupRequestCanceledTimer(taskId, RESPONSE_CALLBACK_WAITING_TIME);
+
+ } catch (RemoteException e) {
+ mLocalLog.log("publish capability exception: " + e.getMessage());
+ logw("publishCapabilities: exception=" + e.getMessage());
+ // Exception occurred, end this request.
+ setRequestEnded(requestResponse);
+ checkAndSendPendingRequest();
+ }
+ }
+
+ /**
+ * Handle the command error callback of the publish request. This method is called by the
+ * handler of the PublishController.
+ */
+ public void onCommandError(PublishRequestResponse requestResponse) {
+ if (!checkRequestRespValid(requestResponse)) {
+ mLocalLog.log("Command error callback is invalid");
+ logw("onCommandError: request response is invalid");
+ setRequestEnded(requestResponse);
+ checkAndSendPendingRequest();
+ return;
+ }
+
+ mLocalLog.log("Receive command error code=" + requestResponse.getCmdErrorCode());
+ logd("onCommandError: " + requestResponse.toString());
+
+ if (requestResponse.needRetry()) {
+ // Increase the retry count
+ mProcessorState.increaseRetryCount();
+
+ // Reset the pending flag since it is going to resend a publish request.
+ mProcessorState.setPendingRequest(false);
+
+ // Resend a publish request
+ long delayTime = mProcessorState.getDelayTimeToAllowPublish();
+ mPublishCtrlCallback.requestPublishFromInternal(
+ PublishController.PUBLISH_TRIGGER_RETRY, delayTime);
+ } else {
+ // Update the publish state if the request is failed and doesn't need to retry.
+ int publishState = requestResponse.getPublishStateByCmdErrorCode();
+ mPublishCtrlCallback.updatePublishRequestResult(publishState);
+
+ // Check if there is a pending request
+ checkAndSendPendingRequest();
+ }
+
+ // End this request
+ setRequestEnded(requestResponse);
+ }
+
+ /**
+ * Handle the network response callback of the publish request. This method is called by the
+ * handler of the PublishController.
+ */
+ public void onNetworkResponse(PublishRequestResponse requestResponse) {
+ if (!checkRequestRespValid(requestResponse)) {
+ mLocalLog.log("Network response callback is invalid");
+ logw("onNetworkResponse: request response is invalid");
+ setRequestEnded(requestResponse);
+ checkAndSendPendingRequest();
+ return;
+ }
+
+ mLocalLog.log("Receive network response code=" + requestResponse.getNetworkRespSipCode());
+ logd("onNetworkResponse: " + requestResponse.toString());
+
+ if (requestResponse.needRetry()) {
+ // Increase the retry count
+ mProcessorState.increaseRetryCount();
+
+ // Reset the pending flag since it is going to resend a publish request.
+ mProcessorState.setPendingRequest(false);
+
+ // Resend a publish request
+ long delayTime = mProcessorState.getDelayTimeToAllowPublish();
+ mPublishCtrlCallback.requestPublishFromInternal(
+ PublishController.PUBLISH_TRIGGER_RETRY, delayTime);
+ } else {
+ // Reset the retry count if the publish is success.
+ if (requestResponse.isRequestSuccess()) {
+ mProcessorState.resetRetryCount();
+ }
+ // Update the publish state if the request doesn't need to retry.
+ int publishResult = requestResponse.getPublishStateByNetworkResponse();
+ mPublishCtrlCallback.updatePublishRequestResult(publishResult);
+
+ // Check if there is a pending request
+ checkAndSendPendingRequest();
+ }
+
+ // End this request
+ setRequestEnded(requestResponse);
+ }
+
+ // Check if the request response callback is valid.
+ private boolean checkRequestRespValid(PublishRequestResponse requestResponse) {
+ if (requestResponse == null) {
+ logd("checkRequestRespValid: request response is null");
+ return false;
+ }
+
+ if (!mProcessorState.isPublishingNow()) {
+ logd("checkRequestRespValid: the request is finished");
+ return false;
+ }
+
+ // Abandon this response callback if the current taskId is different to the response
+ // callback taskId. This response callback is obsoleted.
+ long taskId = mProcessorState.getCurrentTaskId();
+ long responseTaskId = requestResponse.getTaskId();
+ if (taskId != responseTaskId) {
+ logd("checkRequestRespValid: invalid taskId! current taskId=" + taskId
+ + ", response callback taskId=" + responseTaskId);
+ return false;
+ }
+
+ if (mIsDestroyed) {
+ logd("checkRequestRespValid: is already destroyed! taskId=" + taskId);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Cancel the publishing request since it has token too long for waiting the response callback.
+ * This method is called by the handler of the PublishController.
+ */
+ public void cancelPublishRequest(long taskId) {
+ mLocalLog.log("cancel publish request: taskId=" + taskId);
+ logd("cancelPublishRequest: taskId=" + taskId);
+ setRequestEnded(null);
+ checkAndSendPendingRequest();
+ }
+
+ private void setRequestEnded(PublishRequestResponse requestResponse) {
+ long taskId = -1L;
+ if (requestResponse != null) {
+ requestResponse.onDestroy();
+ taskId = requestResponse.getTaskId();
+ }
+ mProcessorState.setPublishingFlag(false);
+ mPublishCtrlCallback.clearRequestCanceledTimer();
+
+ mLocalLog.log("Set request ended: taskId=" + taskId);
+ logd("setRequestEnded: taskId=" + taskId);
+ }
+
+ private void checkAndSendPendingRequest() {
+ if (mIsDestroyed) return;
+ if (mProcessorState.hasPendingRequest()) {
+ logd("checkAndSendPendingRequest: send pending request");
+ mProcessorState.setPublishingFlag(false);
+
+ long delayTime = mProcessorState.getDelayTimeToAllowPublish();
+ mPublishCtrlCallback.requestPublishFromInternal(
+ PublishController.PUBLISH_TRIGGER_RETRY, delayTime);
+ }
+ }
+
+ @VisibleForTesting
+ public void setProcessorState(PublishProcessorState processorState) {
+ mProcessorState = processorState;
+ }
+
+ private void logd(String log) {
+ Log.d(LOG_TAG, getLogPrefix().append(log).toString());
+ }
+
+ private void logi(String log) {
+ Log.i(LOG_TAG, getLogPrefix().append(log).toString());
+ }
+
+ private void logw(String log) {
+ Log.w(LOG_TAG, getLogPrefix().append(log).toString());
+ }
+
+ private StringBuilder getLogPrefix() {
+ StringBuilder builder = new StringBuilder("[");
+ builder.append(mSubId);
+ builder.append("] ");
+ return builder;
+ }
+
+ public void dump(PrintWriter printWriter) {
+ mLocalLog.dump(printWriter);
}
}
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
new file mode 100644
index 00000000..2ac2fef4
--- /dev/null
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 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.ims.rcs.uce.presence.publish;
+
+import android.util.Log;
+
+import com.android.ims.rcs.uce.util.UceUtils;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The helper class to manage the publish request parameters.
+ */
+public class PublishProcessorState {
+
+ private static final String LOG_TAG = "PublishProcessorState";
+
+ // The waiting period before the first retry.
+ private static final int RETRY_BASE_PERIOD = 1; // minute
+
+ // The maximum number of the publication retries.
+ private static final int PUBLISH_MAXIMUM_NUM_RETRIES = 4;
+
+ private long mTaskId;
+ private volatile boolean mIsPublishing;
+ private volatile boolean mPendingRequest;
+
+ private int mRetryCount;
+
+ // The timestamp to allow to execute the publishing request.
+ private Instant mAllowedTimestamp;
+
+ private final Object mLock = new Object();
+
+ public PublishProcessorState() {
+ }
+
+ // Generate a unique task Id for this request.
+ public long generatePublishTaskId() {
+ synchronized (mLock) {
+ mTaskId = UceUtils.generateTaskId();
+ return mTaskId;
+ }
+ }
+
+ public long getCurrentTaskId() {
+ synchronized (mLock) {
+ return mTaskId;
+ }
+ }
+
+ public void setPublishingFlag(boolean flag) {
+ mIsPublishing = flag;
+ }
+
+ public boolean isPublishingNow() {
+ return mIsPublishing;
+ }
+
+ public void setPendingRequest(boolean pendingRequest) {
+ mPendingRequest = pendingRequest;
+ }
+
+ boolean hasPendingRequest() {
+ return mPendingRequest;
+ }
+
+ // Check if it has reached the maximum retry count.
+ public boolean isReachMaximumRetries() {
+ synchronized (mLock) {
+ return (mRetryCount >= PUBLISH_MAXIMUM_NUM_RETRIES) ? true : false;
+ }
+ }
+
+ // Check if the current timestamp is allowed to request publish.
+ public boolean isCurrentTimeAllowed() {
+ synchronized (mLock) {
+ Instant now = Instant.now();
+ return (now.isAfter(mAllowedTimestamp)) ? true : false;
+ }
+ }
+
+ // Get the delay time to allow to execute the publish request.
+ public long getDelayTimeToAllowPublish() {
+ synchronized (mLock) {
+ // Setup the delay to the time which publish request is allowed to execute.
+ long delayTime = ChronoUnit.MILLIS.between(Instant.now(), mAllowedTimestamp);
+ if (delayTime < 0) {
+ delayTime = 0L;
+ }
+ return delayTime;
+ }
+ }
+
+ public void resetRetryCount() {
+ synchronized (mLock) {
+ mRetryCount = 0;
+ // Adjust the allowed timestamp of the publishing request.
+ adjustAllowedPublishTimestamp();
+ }
+ }
+
+ public void increaseRetryCount() {
+ synchronized (mLock) {
+ if (mRetryCount < PUBLISH_MAXIMUM_NUM_RETRIES) {
+ mRetryCount++;
+ }
+ adjustAllowedPublishTimestamp();
+ }
+ }
+
+ // Adjust the timestamp to allow request PUBLISH with the specific delay time
+ private void adjustAllowedPublishTimestamp() {
+ synchronized (mLock) {
+ Log.v(LOG_TAG, "adjustAllowedPublishTimestamp: retry=" + mRetryCount);
+ if (mAllowedTimestamp == null) {
+ // Now for the initialization.
+ mAllowedTimestamp = Instant.now();
+ } else {
+ long nextRetryDuration = getNextRetryDuration();
+ mAllowedTimestamp = Instant.now().plus(Duration.ofMillis(nextRetryDuration));
+ }
+ Log.v(LOG_TAG, "adjustAllowedPublishTimestamp: timestamp="
+ + mAllowedTimestamp.toString());
+ }
+ }
+
+ // Return the milliseconds of the next retry delay.
+ private long getNextRetryDuration() {
+ synchronized (mLock) {
+ int power = mRetryCount - 1;
+ if (power < 0) {
+ power = 0;
+ }
+ // Next retry duration (minute)
+ Double retryDuration = RETRY_BASE_PERIOD * Math.pow(2, power);
+
+ // Convert to millis
+ return TimeUnit.MINUTES.toMillis(retryDuration.longValue());
+ }
+ }
+}
diff --git a/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java b/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java
new file mode 100644
index 00000000..1dac8650
--- /dev/null
+++ b/src/java/com/android/ims/rcs/uce/presence/publish/PublishRequestResponse.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2020 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.ims.rcs.uce.presence.publish;
+
+import android.telephony.ims.ImsException;
+import android.telephony.ims.RcsUceAdapter;
+
+import com.android.ims.rcs.uce.RcsCapabilityExchangeImplAdapter;
+import com.android.ims.rcs.uce.RcsCapabilityExchangeImplAdapter.PublishResponseCallback;
+import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
+import com.android.ims.rcs.uce.util.NetworkSipCode;
+
+/**
+ * Receiving the result callback of the publish request.
+ */
+public class PublishRequestResponse implements PublishResponseCallback {
+
+ private final long mTaskId;
+ private volatile boolean mNeedRetry;
+ private volatile PublishControllerCallback mPublishCtrlCallback;
+
+ private int mCmdErrorCode;
+ private int mNetworkRespSipCode;
+ private String mNetworkRespReason;
+
+ public PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId) {
+ mTaskId = taskId;
+ mPublishCtrlCallback = publishCtrlCallback;
+ }
+
+ public long getTaskId() {
+ return mTaskId;
+ }
+
+ public int getCmdErrorCode() {
+ return mCmdErrorCode;
+ }
+
+ public int getNetworkRespSipCode() {
+ return mNetworkRespSipCode;
+ }
+
+ public void onDestroy() {
+ mPublishCtrlCallback = null;
+ }
+
+ @Override
+ public void onCommandError(int errorCode) throws ImsException {
+ mCmdErrorCode = errorCode;
+ updateRetryFlagByCommandError();
+
+ PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
+ if (ctrlCallback != null) {
+ ctrlCallback.onRequestCommandError(this);
+ }
+ }
+
+ @Override
+ public void onNetworkResponse(int sipCode, String reason) {
+ mNetworkRespSipCode = sipCode;
+ mNetworkRespReason = reason;
+ updateRetryFlagByNetworkResponse();
+
+ PublishControllerCallback ctrlCallback = mPublishCtrlCallback;
+ if (ctrlCallback != null) {
+ ctrlCallback.onRequestNetworkResp(this);
+ }
+ }
+
+ private void updateRetryFlagByCommandError() {
+ switch(mCmdErrorCode) {
+ case RcsCapabilityExchangeImplAdapter.COMMAND_CODE_REQUEST_TIMEOUT:
+ case RcsCapabilityExchangeImplAdapter.COMMAND_CODE_INSUFFICIENT_MEMORY:
+ case RcsCapabilityExchangeImplAdapter.COMMAND_CODE_LOST_NETWORK_CONNECTION:
+ case RcsCapabilityExchangeImplAdapter.COMMAND_CODE_SERVICE_UNAVAILABLE:
+ mNeedRetry = true;
+ break;
+ }
+ }
+
+ private void updateRetryFlagByNetworkResponse() {
+ switch (mNetworkRespSipCode) {
+ case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT:
+ case NetworkSipCode.SIP_CODE_INTERVAL_TOO_BRIEF:
+ case NetworkSipCode.SIP_CODE_TEMPORARILY_UNAVAILABLE:
+ case NetworkSipCode.SIP_CODE_BUSY:
+ case NetworkSipCode.SIP_CODE_SERVER_INTERNAL_ERROR:
+ case NetworkSipCode.SIP_CODE_SERVICE_UNAVAILABLE:
+ case NetworkSipCode.SIP_CODE_SERVER_TIMEOUT:
+ case NetworkSipCode.SIP_CODE_BUSY_EVERYWHERE:
+ case NetworkSipCode.SIP_CODE_DECLINE:
+ mNeedRetry = true;
+ break;
+ }
+ }
+
+ /*
+ * Check whether the publishing request is successful.
+ */
+ public boolean isRequestSuccess() {
+ return (mNetworkRespSipCode == NetworkSipCode.SIP_CODE_OK) ? true : false;
+ }
+
+ /**
+ * Check whether the publishing request needs to be retried.
+ */
+ public boolean needRetry() {
+ return mNeedRetry;
+ }
+
+ /**
+ * Convert the command error code to the publish state
+ */
+ public int getPublishStateByCmdErrorCode() {
+ if (RcsCapabilityExchangeImplAdapter.COMMAND_CODE_REQUEST_TIMEOUT == mCmdErrorCode) {
+ return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
+ }
+ return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ }
+
+ /**
+ * Convert the network sip code to the publish state
+ */
+ public int getPublishStateByNetworkResponse() {
+ if (NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT == mNetworkRespSipCode) {
+ return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT;
+ }
+ return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR;
+ }
+
+ /**
+ * Get the information of the publish request response.
+ */
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("taskId=").append(mTaskId)
+ .append(", mCmdErrorCode=").append(mCmdErrorCode)
+ .append(", onNetworkResponse=").append(mNetworkRespSipCode)
+ .append(", mNetworkResponseReason=").append(mNetworkRespReason)
+ .append(", isRequestSuccess=").append(isRequestSuccess())
+ .append(", needRetry=").append(mNeedRetry);
+ return builder.toString();
+ }
+}
diff --git a/src/java/com/android/ims/rcs/uce/util/NetworkSipCode.java b/src/java/com/android/ims/rcs/uce/util/NetworkSipCode.java
index 21d87fa0..425660a8 100644
--- a/src/java/com/android/ims/rcs/uce/util/NetworkSipCode.java
+++ b/src/java/com/android/ims/rcs/uce/util/NetworkSipCode.java
@@ -21,7 +21,15 @@ package com.android.ims.rcs.uce.util;
*/
public class NetworkSipCode {
public static final int SIP_CODE_OK = 200;
+ public static final int SIP_CODE_REQUEST_TIMEOUT = 408;
+ public static final int SIP_CODE_INTERVAL_TOO_BRIEF = 423;
+ public static final int SIP_CODE_TEMPORARILY_UNAVAILABLE = 480;
+ public static final int SIP_CODE_BUSY = 486;
+ public static final int SIP_CODE_SERVER_INTERNAL_ERROR = 500;
public static final int SIP_CODE_SERVICE_UNAVAILABLE = 503;
+ public static final int SIP_CODE_SERVER_TIMEOUT = 504;
+ public static final int SIP_CODE_BUSY_EVERYWHERE = 600;
+ public static final int SIP_CODE_DECLINE = 603;
public static final String SIP_OK = "OK";
public static final String SIP_SERVICE_UNAVAILABLE = "Service Unavailable";
diff --git a/src/java/com/android/ims/rcs/uce/util/UceUtils.java b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
new file mode 100644
index 00000000..a411a56e
--- /dev/null
+++ b/src/java/com/android/ims/rcs/uce/util/UceUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2020 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.ims.rcs.uce.util;
+
+import android.content.Context;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ProvisioningManager;
+import android.util.Log;
+
+public class UceUtils {
+
+ private static final String LOG_TAG = "UceUtils";
+
+ private static long TASK_ID = 0L;
+
+ /**
+ * Generate the unique UCE request task id.
+ */
+ public static synchronized long generateTaskId() {
+ return ++TASK_ID;
+ }
+
+ public static boolean isEabProvisioned(Context context, int subId) {
+ boolean isProvisioned = false;
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.w(LOG_TAG, "isEabProvisioned: 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);
+ isProvisioned = manager.getProvisioningIntValue(
+ ProvisioningManager.KEY_EAB_PROVISIONING_STATUS)
+ == ProvisioningManager.PROVISIONING_VALUE_ENABLED;
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "isEabProvisioned: exception=" + e.getMessage());
+ }
+ return isProvisioned;
+ }
+}
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
index 23b01fc3..05085f81 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/DeviceCapabilityListenerTest.java
@@ -45,8 +45,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
@RunWith(AndroidJUnit4.class)
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
index 1041a22b..c9ec14ac 100644
--- a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishControllerImplTest.java
@@ -40,8 +40,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
@RunWith(AndroidJUnit4.class)
diff --git a/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java
new file mode 100644
index 00000000..68373dfb
--- /dev/null
+++ b/tests/src/com/android/ims/rcs/uce/presence/publish/PublishProcessorTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2020 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.ims.rcs.uce.presence.publish;
+
+import static android.telephony.ims.RcsContactPresenceTuple.TUPLE_BASIC_STATUS_OPEN;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.net.Uri;
+import android.telephony.ims.RcsContactPresenceTuple;
+import android.telephony.ims.RcsContactUceCapability;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.ims.ImsTestBase;
+import com.android.ims.RcsFeatureManager;
+import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class PublishProcessorTest extends ImsTestBase {
+
+ @Mock RcsFeatureManager mRcsFeatureManager;
+ @Mock DeviceCapabilityInfo mDeviceCapabilities;
+ @Mock PublishControllerCallback mPublishCtrlCallback;
+ @Mock PublishProcessorState mProcessorState;
+ @Mock PublishRequestResponse mResponseCallback;
+
+ private int mSub = 1;
+ private long mTaskId = 1L;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ doReturn(true).when(mProcessorState).isCurrentTimeAllowed();
+ doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
+
+ doReturn(true).when(mDeviceCapabilities).isImsRegistered();
+ RcsContactUceCapability capability = getRcsContactUceCapability();
+ doReturn(capability).when(mDeviceCapabilities).getDeviceCapabilities(any());
+
+ doReturn(mTaskId).when(mResponseCallback).getTaskId();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @SmallTest
+ public void testDoPublish() throws Exception {
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_SERVICE);
+
+ verify(mDeviceCapabilities).getDeviceCapabilities(any());
+ verify(mProcessorState).setPublishingFlag(true);
+ verify(mRcsFeatureManager).requestPublication(any(), any());
+ verify(mPublishCtrlCallback).setupRequestCanceledTimer(anyLong(), anyLong());
+ }
+
+ @Test
+ @SmallTest
+ public void testDoPublishWithResetRetryCount() throws Exception {
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_SERVICE);
+
+ verify(mProcessorState).resetRetryCount();
+ }
+
+ @Test
+ @SmallTest
+ public void testPublishWithoutResetRetryCount() throws Exception {
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
+
+ verify(mProcessorState, never()).resetRetryCount();
+ }
+
+ @Test
+ @SmallTest
+ public void testNotPublishWhenImsNotRegistered() throws Exception {
+ doReturn(false).when(mDeviceCapabilities).isImsRegistered();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
+
+ verify(mRcsFeatureManager, never()).requestPublication(any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testNotPublishWhenAlreadyPublishing() throws Exception {
+ doReturn(true).when(mProcessorState).isPublishingNow();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
+
+ verify(mProcessorState).setPendingRequest(true);
+ verify(mRcsFeatureManager, never()).requestPublication(any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testNotPublishWhenReachMaximumRetries() throws Exception {
+ doReturn(true).when(mProcessorState).isReachMaximumRetries();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
+
+ verify(mRcsFeatureManager, never()).requestPublication(any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testNotPublishWhenCurrentTimeNotAllowed() throws Exception {
+ doReturn(false).when(mProcessorState).isCurrentTimeAllowed();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.doPublish(PublishController.PUBLISH_TRIGGER_RETRY);
+
+ verify(mPublishCtrlCallback).requestPublishFromInternal(
+ eq(PublishController.PUBLISH_TRIGGER_RETRY), anyLong());
+ verify(mRcsFeatureManager, never()).requestPublication(any(), any());
+ }
+
+ @Test
+ @SmallTest
+ public void testCommandErrorWithRetry() throws Exception {
+ doReturn(true).when(mProcessorState).isPublishingNow();
+ doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
+ doReturn(mTaskId).when(mResponseCallback).getTaskId();
+ doReturn(true).when(mResponseCallback).needRetry();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.onCommandError(mResponseCallback);
+
+ verify(mProcessorState).increaseRetryCount();
+ verify(mPublishCtrlCallback).requestPublishFromInternal(
+ eq(PublishController.PUBLISH_TRIGGER_RETRY), anyLong());
+ verify(mResponseCallback).onDestroy();
+ verify(mProcessorState).setPublishingFlag(false);
+ verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+ }
+
+ @Test
+ @SmallTest
+ public void testCommandErrorWithoutRetry() throws Exception {
+ doReturn(true).when(mProcessorState).isPublishingNow();
+ doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
+ doReturn(mTaskId).when(mResponseCallback).getTaskId();
+ doReturn(false).when(mResponseCallback).needRetry();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.onCommandError(mResponseCallback);
+
+ verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt());
+ verify(mResponseCallback).onDestroy();
+ verify(mProcessorState).setPublishingFlag(false);
+ verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+ }
+
+ @Test
+ @SmallTest
+ public void testNetworkResponseWithRetry() throws Exception {
+ doReturn(true).when(mProcessorState).isPublishingNow();
+ doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
+ doReturn(mTaskId).when(mResponseCallback).getTaskId();
+ doReturn(true).when(mResponseCallback).needRetry();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.onNetworkResponse(mResponseCallback);
+
+ verify(mProcessorState).increaseRetryCount();
+ verify(mPublishCtrlCallback).requestPublishFromInternal(
+ eq(PublishController.PUBLISH_TRIGGER_RETRY), anyLong());
+ verify(mResponseCallback).onDestroy();
+ verify(mProcessorState).setPublishingFlag(false);
+ verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+ }
+
+ @Test
+ @SmallTest
+ public void testNetworkResponseSuccessful() throws Exception {
+ doReturn(true).when(mProcessorState).isPublishingNow();
+ doReturn(mTaskId).when(mProcessorState).getCurrentTaskId();
+ doReturn(mTaskId).when(mResponseCallback).getTaskId();
+ doReturn(false).when(mResponseCallback).needRetry();
+ doReturn(true).when(mResponseCallback).isRequestSuccess();
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.onNetworkResponse(mResponseCallback);
+
+ verify(mPublishCtrlCallback).updatePublishRequestResult(anyInt());
+ verify(mResponseCallback).onDestroy();
+ verify(mProcessorState).setPublishingFlag(false);
+ verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+ }
+
+ @Test
+ @SmallTest
+ public void testCancelPublishRequest() throws Exception {
+ PublishProcessor publishProcessor = getPublishProcessor();
+
+ publishProcessor.cancelPublishRequest(mTaskId);
+
+ verify(mProcessorState).setPublishingFlag(false);
+ verify(mPublishCtrlCallback).clearRequestCanceledTimer();
+ }
+
+ private PublishProcessor getPublishProcessor() {
+ PublishProcessor publishProcessor = new PublishProcessor(mContext, mSub,
+ mDeviceCapabilities, mPublishCtrlCallback);
+ publishProcessor.setProcessorState(mProcessorState);
+ publishProcessor.onRcsConnected(mRcsFeatureManager);
+ return publishProcessor;
+ }
+
+ private RcsContactUceCapability getRcsContactUceCapability() {
+ Uri contact = Uri.fromParts("sip", "test", null);
+
+ RcsContactUceCapability.PresenceBuilder builder =
+ new RcsContactUceCapability.PresenceBuilder(contact,
+ RcsContactUceCapability.SOURCE_TYPE_CACHED,
+ RcsContactUceCapability.REQUEST_RESULT_FOUND);
+
+ RcsContactPresenceTuple.Builder tupleBuilder = new RcsContactPresenceTuple.Builder(
+ TUPLE_BASIC_STATUS_OPEN, RcsContactPresenceTuple.SERVICE_ID_MMTEL, "1.0");
+
+ builder.addCapabilityTuple(tupleBuilder.build());
+ return builder.build();
+ }
+}