1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ims.rcs.uce.presence.publish; 18 19 import android.annotation.Nullable; 20 import android.telephony.ims.RcsUceAdapter; 21 import android.telephony.ims.aidl.IPublishResponseCallback; 22 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; 23 import android.util.Log; 24 25 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback; 26 import com.android.ims.rcs.uce.util.NetworkSipCode; 27 import com.android.ims.rcs.uce.util.UceUtils; 28 29 import java.time.Instant; 30 import java.util.Optional; 31 32 /** 33 * Receiving the result callback of the publish request. 34 */ 35 public class PublishRequestResponse { 36 37 private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishRequestResp"; 38 39 private final long mTaskId; 40 private final String mPidfXml; 41 private volatile boolean mNeedRetry; 42 private volatile PublishControllerCallback mPublishCtrlCallback; 43 44 private Optional<Integer> mCmdErrorCode; 45 private Optional<Integer> mNetworkRespSipCode; 46 private Optional<String> mReasonPhrase; 47 private Optional<Integer> mReasonHeaderCause; 48 private Optional<String> mReasonHeaderText; 49 50 // The timestamp when receive the response from the network. 51 private Instant mResponseTimestamp; 52 PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId, String pidfXml)53 public PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId, 54 String pidfXml) { 55 mTaskId = taskId; 56 mPidfXml = pidfXml; 57 mPublishCtrlCallback = publishCtrlCallback; 58 mCmdErrorCode = Optional.empty(); 59 mNetworkRespSipCode = Optional.empty(); 60 mReasonPhrase = Optional.empty(); 61 mReasonHeaderCause = Optional.empty(); 62 mReasonHeaderText = Optional.empty(); 63 } 64 65 // The result callback of the publish capability request. 66 private IPublishResponseCallback mResponseCallback = new IPublishResponseCallback.Stub() { 67 @Override 68 public void onCommandError(int code) { 69 PublishRequestResponse.this.onCommandError(code); 70 } 71 72 @Override 73 public void onNetworkResponse(int code, String reason) { 74 PublishRequestResponse.this.onNetworkResponse(code, reason); 75 } 76 77 @Override 78 public void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause, 79 String reasonHeaderText) { 80 PublishRequestResponse.this.onNetworkResponse(code, reasonPhrase, reasonHeaderCause, 81 reasonHeaderText); 82 } 83 }; 84 getResponseCallback()85 public IPublishResponseCallback getResponseCallback() { 86 return mResponseCallback; 87 } 88 getTaskId()89 public long getTaskId() { 90 return mTaskId; 91 } 92 93 /** 94 * Retrieve the command error code which received from the network. 95 */ getCmdErrorCode()96 public Optional<Integer> getCmdErrorCode() { 97 return mCmdErrorCode; 98 } 99 100 /** 101 * Retrieve the network response sip code which received from the network. 102 */ getNetworkRespSipCode()103 public Optional<Integer> getNetworkRespSipCode() { 104 return mNetworkRespSipCode; 105 } 106 107 /** 108 * Retrieve the reason phrase of the network response which received from the network. 109 */ getReasonPhrase()110 public Optional<String> getReasonPhrase() { 111 return mReasonPhrase; 112 } 113 114 /** 115 * Retrieve the reason header from the network response. 116 */ getReasonHeaderCause()117 public Optional<Integer> getReasonHeaderCause() { 118 return mReasonHeaderCause; 119 } 120 121 /** 122 * Retrieve the description of the reason header. 123 */ getReasonHeaderText()124 public Optional<String> getReasonHeaderText() { 125 return mReasonHeaderText; 126 } 127 128 /** 129 * Retrieve the SIP code from the network response. It will get the value from the Reason 130 * Header first. If the ReasonHeader is not present, it will get the value from the Network 131 * response instead. 132 */ getResponseSipCode()133 public Optional<Integer> getResponseSipCode() { 134 return (mReasonHeaderCause.isPresent()) ? mReasonHeaderCause : mNetworkRespSipCode; 135 } 136 137 /** 138 * Retrieve the REASON from the network response. It will get the value from the Reason Header 139 * first. If the ReasonHeader is not present, it will get the value from the Network response 140 * instead. 141 */ getResponseReason()142 public Optional<String> getResponseReason() { 143 return (mReasonHeaderText.isPresent()) ? mReasonHeaderText : mReasonPhrase; 144 } 145 146 /** 147 * Get the timestamp of receiving the network response callback. 148 */ getResponseTimestamp()149 public @Nullable Instant getResponseTimestamp() { 150 return mResponseTimestamp; 151 } 152 153 /** 154 * @return the PIDF XML sent during this request. 155 */ getPidfXml()156 public String getPidfXml() { 157 return mPidfXml; 158 } 159 onDestroy()160 public void onDestroy() { 161 mPublishCtrlCallback = null; 162 } 163 onCommandError(int errorCode)164 private void onCommandError(int errorCode) { 165 mResponseTimestamp = Instant.now(); 166 mCmdErrorCode = Optional.of(errorCode); 167 updateRetryFlagByCommandError(); 168 169 PublishControllerCallback ctrlCallback = mPublishCtrlCallback; 170 if (ctrlCallback != null) { 171 ctrlCallback.onRequestCommandError(this); 172 } else { 173 Log.d(LOG_TAG, "onCommandError: already destroyed. error code=" + errorCode); 174 } 175 } 176 onNetworkResponse(int sipCode, String reason)177 private void onNetworkResponse(int sipCode, String reason) { 178 mResponseTimestamp = Instant.now(); 179 mNetworkRespSipCode = Optional.of(sipCode); 180 mReasonPhrase = Optional.ofNullable(reason); 181 updateRetryFlagByNetworkResponse(); 182 183 PublishControllerCallback ctrlCallback = mPublishCtrlCallback; 184 if (ctrlCallback != null) { 185 ctrlCallback.onRequestNetworkResp(this); 186 } else { 187 Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sip code=" + sipCode); 188 } 189 } 190 onNetworkResponse(int sipCode, String reasonPhrase, int reasonHeaderCause, String reasonHeaderText)191 private void onNetworkResponse(int sipCode, String reasonPhrase, int reasonHeaderCause, 192 String reasonHeaderText) { 193 mResponseTimestamp = Instant.now(); 194 mNetworkRespSipCode = Optional.of(sipCode); 195 mReasonPhrase = Optional.ofNullable(reasonPhrase); 196 mReasonHeaderCause = Optional.of(reasonHeaderCause); 197 mReasonHeaderText = Optional.ofNullable(reasonHeaderText); 198 updateRetryFlagByNetworkResponse(); 199 200 PublishControllerCallback ctrlCallback = mPublishCtrlCallback; 201 if (ctrlCallback != null) { 202 ctrlCallback.onRequestNetworkResp(this); 203 } else { 204 Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sipCode=" + sipCode + 205 ", reasonHeader=" + reasonHeaderCause); 206 } 207 } 208 updateRetryFlagByCommandError()209 private void updateRetryFlagByCommandError() { 210 switch(getCmdErrorCode().orElse(-1)) { 211 case RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT: 212 case RcsCapabilityExchangeImplBase.COMMAND_CODE_INSUFFICIENT_MEMORY: 213 case RcsCapabilityExchangeImplBase.COMMAND_CODE_LOST_NETWORK_CONNECTION: 214 case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNAVAILABLE: 215 mNeedRetry = true; 216 break; 217 } 218 } 219 updateRetryFlagByNetworkResponse()220 private void updateRetryFlagByNetworkResponse() { 221 // Disable retry flag because the retry mechanism is implemented in the ImsService. 222 mNeedRetry = false; 223 } 224 225 /* 226 * Check whether the publishing request is successful. 227 */ isRequestSuccess()228 public boolean isRequestSuccess() { 229 if (isCommandError()) { 230 return false; 231 } 232 // The result of the request was treated as successful if the command error code is present 233 // and its value is COMMAND_CODE_NO_CHANGE. 234 if (isCommandCodeNoChange()) { 235 return true; 236 } 237 238 final int sipCodeOk = NetworkSipCode.SIP_CODE_OK; 239 if (getNetworkRespSipCode().filter(c -> c == sipCodeOk).isPresent() && 240 (!getReasonHeaderCause().isPresent() 241 || getReasonHeaderCause().filter(c -> c == sipCodeOk).isPresent())) { 242 return true; 243 } 244 return false; 245 } 246 247 /** 248 * Check if the PUBLISH request is failed with receiving the command error. 249 * @return true if the command is failure. 250 */ isCommandError()251 private boolean isCommandError() { 252 // The request is failed if the command error code is present and its value is not 253 // COMMAND_CODE_NO_CHANGE. 254 if (getCmdErrorCode().isPresent() && !isCommandCodeNoChange()) { 255 return true; 256 } 257 return false; 258 } 259 260 // @return true If it received the command code COMMAND_CODE_NO_CHANGE isCommandCodeNoChange()261 private boolean isCommandCodeNoChange() { 262 if (getCmdErrorCode().filter(code -> 263 code == RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE).isPresent()) { 264 return true; 265 } 266 return false; 267 } 268 269 /** 270 * Check whether the publishing request needs to be retried. 271 */ needRetry()272 public boolean needRetry() { 273 return mNeedRetry; 274 } 275 276 /** 277 * @return The publish state when the publish request is finished. 278 */ getPublishState()279 public int getPublishState() { 280 if (isCommandError()) { 281 return getPublishStateByCmdErrorCode(); 282 } else { 283 return getPublishStateByNetworkResponse(); 284 } 285 } 286 287 /** 288 * Convert the command error code to the publish state 289 */ getPublishStateByCmdErrorCode()290 private int getPublishStateByCmdErrorCode() { 291 if (getCmdErrorCode().orElse(-1) == 292 RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT) { 293 return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT; 294 } 295 return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR; 296 } 297 298 /** 299 * Convert the network sip code to the publish state 300 */ getPublishStateByNetworkResponse()301 private int getPublishStateByNetworkResponse() { 302 int respSipCode; 303 if (isCommandCodeNoChange()) { 304 // If the command code is COMMAND_CODE_NO_CHANGE, it should be treated as successful. 305 respSipCode = NetworkSipCode.SIP_CODE_OK; 306 } else if (getReasonHeaderCause().isPresent()) { 307 respSipCode = getReasonHeaderCause().get(); 308 } else { 309 respSipCode = getNetworkRespSipCode().orElse(-1); 310 } 311 312 switch (respSipCode) { 313 case NetworkSipCode.SIP_CODE_OK: 314 return RcsUceAdapter.PUBLISH_STATE_OK; 315 case NetworkSipCode.SIP_CODE_FORBIDDEN: 316 case NetworkSipCode.SIP_CODE_NOT_FOUND: 317 return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR; 318 case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT: 319 return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT; 320 default: 321 return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR; 322 } 323 } 324 325 /** 326 * Get the information of the publish request response. 327 */ 328 @Override toString()329 public String toString() { 330 StringBuilder builder = new StringBuilder(); 331 builder.append("taskId=").append(mTaskId) 332 .append(", CmdErrorCode=").append(getCmdErrorCode().orElse(-1)) 333 .append(", NetworkRespSipCode=").append(getNetworkRespSipCode().orElse(-1)) 334 .append(", ReasonPhrase=").append(getReasonPhrase().orElse("")) 335 .append(", ReasonHeaderCause=").append(getReasonHeaderCause().orElse(-1)) 336 .append(", ReasonHeaderText=").append(getReasonHeaderText().orElse("")) 337 .append(", ResponseTimestamp=").append(mResponseTimestamp) 338 .append(", isRequestSuccess=").append(isRequestSuccess()) 339 .append(", needRetry=").append(mNeedRetry); 340 return builder.toString(); 341 } 342 } 343