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.NonNull; 20 import android.annotation.Nullable; 21 import android.telephony.ims.RcsUceAdapter; 22 import android.telephony.ims.SipDetails; 23 import android.telephony.ims.aidl.IPublishResponseCallback; 24 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; 25 import android.text.TextUtils; 26 import android.util.Log; 27 28 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishControllerCallback; 29 import com.android.ims.rcs.uce.util.NetworkSipCode; 30 import com.android.ims.rcs.uce.util.UceUtils; 31 32 import java.time.Instant; 33 import java.util.Optional; 34 35 /** 36 * Receiving the result callback of the publish request. 37 */ 38 public class PublishRequestResponse { 39 40 private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishRequestResp"; 41 42 private final long mTaskId; 43 private final String mPidfXml; 44 private volatile boolean mNeedRetry; 45 private volatile PublishControllerCallback mPublishCtrlCallback; 46 47 private Optional<Integer> mCmdErrorCode; 48 private Optional<Integer> mNetworkRespSipCode; 49 private Optional<String> mReasonPhrase; 50 private Optional<Integer> mReasonHeaderCause; 51 private Optional<String> mReasonHeaderText; 52 private Optional<SipDetails> mSipDetails; 53 // The timestamp when receive the response from the network. 54 private Instant mResponseTimestamp; 55 PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId, String pidfXml)56 public PublishRequestResponse(PublishControllerCallback publishCtrlCallback, long taskId, 57 String pidfXml) { 58 mTaskId = taskId; 59 mPidfXml = pidfXml; 60 mPublishCtrlCallback = publishCtrlCallback; 61 mCmdErrorCode = Optional.empty(); 62 mNetworkRespSipCode = Optional.empty(); 63 mReasonPhrase = Optional.empty(); 64 mReasonHeaderCause = Optional.empty(); 65 mReasonHeaderText = Optional.empty(); 66 mSipDetails = Optional.empty(); 67 } 68 PublishRequestResponse(String pidfXml, @Nullable SipDetails details)69 public PublishRequestResponse(String pidfXml, @Nullable SipDetails details) { 70 mTaskId = 0L; 71 mPublishCtrlCallback = null; 72 mCmdErrorCode = Optional.empty(); 73 74 mPidfXml = pidfXml; 75 mResponseTimestamp = Instant.now(); 76 mNetworkRespSipCode = Optional.of(details.getResponseCode()); 77 mReasonPhrase = Optional.ofNullable(details.getResponsePhrase()); 78 if (details.getReasonHeaderCause() != 0) { 79 mReasonHeaderCause = Optional.of(details.getReasonHeaderCause()); 80 } else { 81 mReasonHeaderCause = Optional.empty(); 82 } 83 if (TextUtils.isEmpty(details.getReasonHeaderText())) { 84 mReasonHeaderText = Optional.empty(); 85 } else { 86 mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText()); 87 } 88 mSipDetails = Optional.ofNullable(details); 89 } 90 91 // The result callback of the publish capability request. 92 private IPublishResponseCallback mResponseCallback = new IPublishResponseCallback.Stub() { 93 @Override 94 public void onCommandError(int code) { 95 PublishRequestResponse.this.onCommandError(code); 96 } 97 98 @Override 99 public void onNetworkResponse(@NonNull SipDetails details) { 100 PublishRequestResponse.this.onNetworkResponse(details); 101 } 102 }; 103 getResponseCallback()104 public IPublishResponseCallback getResponseCallback() { 105 return mResponseCallback; 106 } 107 getTaskId()108 public long getTaskId() { 109 return mTaskId; 110 } 111 112 /** 113 * Retrieve the command error code which received from the network. 114 */ getCmdErrorCode()115 public Optional<Integer> getCmdErrorCode() { 116 return mCmdErrorCode; 117 } 118 119 /** 120 * Retrieve the network response sip code which received from the network. 121 */ getNetworkRespSipCode()122 public Optional<Integer> getNetworkRespSipCode() { 123 return mNetworkRespSipCode; 124 } 125 126 /** 127 * Retrieve the reason phrase of the network response which received from the network. 128 */ getReasonPhrase()129 public Optional<String> getReasonPhrase() { 130 return mReasonPhrase; 131 } 132 133 /** 134 * Retrieve the reason header from the network response. 135 */ getReasonHeaderCause()136 public Optional<Integer> getReasonHeaderCause() { 137 return mReasonHeaderCause; 138 } 139 140 /** 141 * Retrieve the description of the reason header. 142 */ getReasonHeaderText()143 public Optional<String> getReasonHeaderText() { 144 return mReasonHeaderText; 145 } 146 147 /** 148 * Retrieve the sip information which received from the network. 149 */ getSipDetails()150 public Optional<SipDetails> getSipDetails() { 151 return mSipDetails; 152 } 153 /** 154 * Retrieve the SIP code from the network response. It will get the value from the Reason 155 * Header first. If the ReasonHeader is not present, it will get the value from the Network 156 * response instead. 157 */ getResponseSipCode()158 public Optional<Integer> getResponseSipCode() { 159 return (mReasonHeaderCause.isPresent()) ? mReasonHeaderCause : mNetworkRespSipCode; 160 } 161 162 /** 163 * Retrieve the REASON from the network response. It will get the value from the Reason Header 164 * first. If the ReasonHeader is not present, it will get the value from the Network response 165 * instead. 166 */ getResponseReason()167 public Optional<String> getResponseReason() { 168 return (mReasonHeaderText.isPresent()) ? mReasonHeaderText : mReasonPhrase; 169 } 170 171 /** 172 * Get the timestamp of receiving the network response callback. 173 */ getResponseTimestamp()174 public @Nullable Instant getResponseTimestamp() { 175 return mResponseTimestamp; 176 } 177 178 /** 179 * @return the PIDF XML sent during this request. 180 */ getPidfXml()181 public String getPidfXml() { 182 return mPidfXml; 183 } 184 onDestroy()185 public void onDestroy() { 186 mPublishCtrlCallback = null; 187 } 188 onCommandError(int errorCode)189 private void onCommandError(int errorCode) { 190 mResponseTimestamp = Instant.now(); 191 mCmdErrorCode = Optional.of(errorCode); 192 updateRetryFlagByCommandError(); 193 194 PublishControllerCallback ctrlCallback = mPublishCtrlCallback; 195 if (ctrlCallback != null) { 196 ctrlCallback.onRequestCommandError(this); 197 } else { 198 Log.d(LOG_TAG, "onCommandError: already destroyed. error code=" + errorCode); 199 } 200 } 201 onNetworkResponse(@onNull SipDetails details)202 private void onNetworkResponse(@NonNull SipDetails details) { 203 // When we send a request to PUBLISH and there is no change to the UCE capabilities, we 204 // expected onCommandError() with COMMAND_CODE_NO_CHANGE. 205 // But some of the vendor will instead send SIP code 999. 206 if (details.getResponseCode() == 999) { 207 onCommandError(RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE); 208 return; 209 } 210 mResponseTimestamp = Instant.now(); 211 mNetworkRespSipCode = Optional.of(details.getResponseCode()); 212 mReasonPhrase = Optional.ofNullable(details.getResponsePhrase()); 213 if (details.getReasonHeaderCause() != 0) { 214 mReasonHeaderCause = Optional.of(details.getReasonHeaderCause()); 215 } 216 if (TextUtils.isEmpty(details.getReasonHeaderText())) { 217 mReasonHeaderText = Optional.empty(); 218 } else { 219 mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText()); 220 } 221 mSipDetails = Optional.ofNullable(details); 222 updateRetryFlagByNetworkResponse(); 223 224 PublishControllerCallback ctrlCallback = mPublishCtrlCallback; 225 if (ctrlCallback != null) { 226 ctrlCallback.onRequestNetworkResp(this); 227 } else { 228 Log.d(LOG_TAG, "onNetworkResponse: already destroyed. sip code=" 229 + details.getResponseCode()); 230 } 231 } 232 updateRetryFlagByCommandError()233 private void updateRetryFlagByCommandError() { 234 switch(getCmdErrorCode().orElse(-1)) { 235 case RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT: 236 case RcsCapabilityExchangeImplBase.COMMAND_CODE_INSUFFICIENT_MEMORY: 237 case RcsCapabilityExchangeImplBase.COMMAND_CODE_LOST_NETWORK_CONNECTION: 238 case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNAVAILABLE: 239 mNeedRetry = true; 240 break; 241 } 242 } 243 updateRetryFlagByNetworkResponse()244 private void updateRetryFlagByNetworkResponse() { 245 // Disable retry flag because the retry mechanism is implemented in the ImsService. 246 mNeedRetry = false; 247 } 248 249 /* 250 * Check whether the publishing request is successful. 251 */ isRequestSuccess()252 public boolean isRequestSuccess() { 253 if (isCommandError()) { 254 return false; 255 } 256 // The result of the request was treated as successful if the command error code is present 257 // and its value is COMMAND_CODE_NO_CHANGE. 258 if (isCommandCodeNoChange()) { 259 return true; 260 } 261 262 final int sipCodeOk = NetworkSipCode.SIP_CODE_OK; 263 if (getNetworkRespSipCode().filter(c -> c == sipCodeOk).isPresent() && 264 (!getReasonHeaderCause().isPresent() 265 || getReasonHeaderCause().filter(c -> c == sipCodeOk).isPresent())) { 266 return true; 267 } 268 return false; 269 } 270 271 /** 272 * Check if the PUBLISH request is failed with receiving the command error. 273 * @return true if the command is failure. 274 */ isCommandError()275 private boolean isCommandError() { 276 // The request is failed if the command error code is present and its value is not 277 // COMMAND_CODE_NO_CHANGE. 278 if (getCmdErrorCode().isPresent() && !isCommandCodeNoChange()) { 279 return true; 280 } 281 return false; 282 } 283 284 // @return true If it received the command code COMMAND_CODE_NO_CHANGE isCommandCodeNoChange()285 private boolean isCommandCodeNoChange() { 286 if (getCmdErrorCode().filter(code -> 287 code == RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE).isPresent()) { 288 return true; 289 } 290 return false; 291 } 292 293 /** 294 * Check whether the publishing request needs to be retried. 295 */ needRetry()296 public boolean needRetry() { 297 return mNeedRetry; 298 } 299 300 /** 301 * @return The publish state when the publish request is finished. 302 */ getPublishState()303 public int getPublishState() { 304 if (isCommandError()) { 305 return getPublishStateByCmdErrorCode(); 306 } else { 307 return getPublishStateByNetworkResponse(); 308 } 309 } 310 311 /** 312 * Convert the command error code to the publish state 313 */ getPublishStateByCmdErrorCode()314 private int getPublishStateByCmdErrorCode() { 315 if (getCmdErrorCode().orElse(-1) == 316 RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT) { 317 return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT; 318 } 319 return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR; 320 } 321 322 /** 323 * Convert the network sip code to the publish state 324 */ getPublishStateByNetworkResponse()325 private int getPublishStateByNetworkResponse() { 326 int respSipCode; 327 if (isCommandCodeNoChange()) { 328 // If the command code is COMMAND_CODE_NO_CHANGE, it should be treated as successful. 329 respSipCode = NetworkSipCode.SIP_CODE_OK; 330 } else if (getReasonHeaderCause().isPresent()) { 331 respSipCode = getReasonHeaderCause().get(); 332 } else { 333 respSipCode = getNetworkRespSipCode().orElse(-1); 334 } 335 336 switch (respSipCode) { 337 case NetworkSipCode.SIP_CODE_OK: 338 return RcsUceAdapter.PUBLISH_STATE_OK; 339 case NetworkSipCode.SIP_CODE_FORBIDDEN: 340 case NetworkSipCode.SIP_CODE_NOT_FOUND: 341 case NetworkSipCode.SIP_CODE_SERVER_TIMEOUT: 342 return RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR; 343 case NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT: 344 return RcsUceAdapter.PUBLISH_STATE_REQUEST_TIMEOUT; 345 default: 346 return RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR; 347 } 348 } 349 350 /** 351 * Get the information of the publish request response. 352 */ 353 @Override toString()354 public String toString() { 355 StringBuilder builder = new StringBuilder(); 356 builder.append("taskId=").append(mTaskId) 357 .append(", CmdErrorCode=").append(getCmdErrorCode().orElse(-1)) 358 .append(", NetworkRespSipCode=").append(getNetworkRespSipCode().orElse(-1)) 359 .append(", ReasonPhrase=").append(getReasonPhrase().orElse("")) 360 .append(", ReasonHeaderCause=").append(getReasonHeaderCause().orElse(-1)) 361 .append(", ReasonHeaderText=").append(getReasonHeaderText().orElse("")) 362 .append(", ResponseTimestamp=").append(mResponseTimestamp) 363 .append(", isRequestSuccess=").append(isRequestSuccess()) 364 .append(", needRetry=").append(mNeedRetry); 365 return builder.toString(); 366 } 367 } 368