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.request; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.net.Uri; 22 import android.telephony.ims.RcsContactTerminatedReason; 23 import android.telephony.ims.RcsContactUceCapability; 24 import android.telephony.ims.RcsUceAdapter; 25 import android.telephony.ims.RcsUceAdapter.ErrorCode; 26 import android.telephony.ims.SipDetails; 27 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; 28 import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode; 29 import android.text.TextUtils; 30 import android.util.Log; 31 32 import com.android.ims.rcs.uce.UceController; 33 import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils; 34 import com.android.ims.rcs.uce.util.NetworkSipCode; 35 import com.android.ims.rcs.uce.util.UceUtils; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.HashMap; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Objects; 44 import java.util.Optional; 45 import java.util.Set; 46 import java.util.stream.Collectors; 47 48 /** 49 * The container of the result of the capabilities request. 50 */ 51 public class CapabilityRequestResponse { 52 53 private static final String LOG_TAG = UceUtils.getLogPrefix() + "CapabilityRequestResp"; 54 55 // The error code when the request encounters internal errors. 56 private @ErrorCode Optional<Integer> mRequestInternalError; 57 58 // The command error code of the request. It is assigned by the callback "onCommandError" 59 private @CommandCode Optional<Integer> mCommandError; 60 61 // The SIP code and reason of the network response. 62 private Optional<Integer> mNetworkRespSipCode; 63 private Optional<String> mReasonPhrase; 64 65 // The SIP code and the phrase read from the reason header 66 private Optional<Integer> mReasonHeaderCause; 67 private Optional<String> mReasonHeaderText; 68 69 // The reason why the this request was terminated and how long after it can be retried. 70 // This value is assigned by the callback "onTerminated" 71 private Optional<String> mTerminatedReason; 72 private Optional<Long> mRetryAfterMillis; 73 74 // The list of the valid capabilities which is retrieved from the cache. 75 private List<RcsContactUceCapability> mCachedCapabilityList; 76 77 // The list of the updated capabilities. This is assigned by the callback 78 // "onNotifyCapabilitiesUpdate" 79 private List<RcsContactUceCapability> mUpdatedCapabilityList; 80 81 // The list of the terminated resource. This is assigned by the callback 82 // "onResourceTerminated" 83 private List<RcsContactUceCapability> mTerminatedResource; 84 85 // The list of the remote contact's capability. 86 private Set<String> mRemoteCaps; 87 88 // The collection to record whether the request contacts have received the capabilities updated. 89 private Map<Uri, Boolean> mContactCapsReceived; 90 91 // The SIP detail information of the network response. 92 private Optional<SipDetails> mSipDetails; 93 CapabilityRequestResponse()94 public CapabilityRequestResponse() { 95 mRequestInternalError = Optional.empty(); 96 mCommandError = Optional.empty(); 97 mNetworkRespSipCode = Optional.empty(); 98 mReasonPhrase = Optional.empty(); 99 mReasonHeaderCause = Optional.empty(); 100 mReasonHeaderText = Optional.empty(); 101 mTerminatedReason = Optional.empty(); 102 mRetryAfterMillis = Optional.of(0L); 103 mTerminatedResource = new ArrayList<>(); 104 mCachedCapabilityList = new ArrayList<>(); 105 mUpdatedCapabilityList = new ArrayList<>(); 106 mRemoteCaps = new HashSet<>(); 107 mContactCapsReceived = new HashMap<>(); 108 mSipDetails = Optional.empty(); 109 } 110 111 /** 112 * Set the request contacts which is expected to receive the capabilities updated. 113 */ setRequestContacts(List<Uri> contactUris)114 public synchronized void setRequestContacts(List<Uri> contactUris) { 115 // Initialize the default value to FALSE. All the numbers have not received the 116 // capabilities updated. 117 contactUris.forEach(contact -> mContactCapsReceived.put(contact, Boolean.FALSE)); 118 Log.d(LOG_TAG, "setRequestContacts: size=" + mContactCapsReceived.size()); 119 } 120 121 /** 122 * Get the contacts that have not received the capabilities updated yet. 123 */ getNotReceiveCapabilityUpdatedContact()124 public synchronized List<Uri> getNotReceiveCapabilityUpdatedContact() { 125 return mContactCapsReceived.entrySet() 126 .stream() 127 .filter(entry -> Objects.equals(entry.getValue(), Boolean.FALSE)) 128 .map(Map.Entry::getKey) 129 .collect(Collectors.toList()); 130 } 131 132 /** 133 * Set the request contacts which is expected to receive the capabilities updated. 134 */ haveAllRequestCapsUpdatedBeenReceived()135 public synchronized boolean haveAllRequestCapsUpdatedBeenReceived() { 136 return !(mContactCapsReceived.containsValue(Boolean.FALSE)); 137 } 138 139 /** 140 * Set the error code when the request encounters internal unexpected errors. 141 * @param errorCode the error code of the internal request error. 142 */ setRequestInternalError(@rrorCode int errorCode)143 public synchronized void setRequestInternalError(@ErrorCode int errorCode) { 144 mRequestInternalError = Optional.of(errorCode); 145 } 146 147 /** 148 * Get the request internal error code. 149 */ getRequestInternalError()150 public synchronized Optional<Integer> getRequestInternalError() { 151 return mRequestInternalError; 152 } 153 154 /** 155 * Set the command error code which is sent from ImsService and set the capability error code. 156 */ setCommandError(@ommandCode int commandError)157 public synchronized void setCommandError(@CommandCode int commandError) { 158 mCommandError = Optional.of(commandError); 159 } 160 161 /** 162 * Get the command error codeof this request. 163 */ getCommandError()164 public synchronized Optional<Integer> getCommandError() { 165 return mCommandError; 166 } 167 168 /** 169 * Set the network response of this request which is sent by the network. 170 */ setNetworkResponseCode(int sipCode, String reason)171 public synchronized void setNetworkResponseCode(int sipCode, String reason) { 172 mNetworkRespSipCode = Optional.of(sipCode); 173 mReasonPhrase = Optional.ofNullable(reason); 174 } 175 176 /** 177 * Set the network response of this request which is sent by the network. 178 */ setSipDetails(@onNull SipDetails details)179 public synchronized void setSipDetails(@NonNull SipDetails details) { 180 setNetworkResponseCode(details.getResponseCode(), details.getResponsePhrase()); 181 if (details.getReasonHeaderCause() != 0) { 182 mReasonHeaderCause = Optional.of(details.getReasonHeaderCause()); 183 } else { 184 mReasonHeaderCause = Optional.empty(); 185 } 186 if (TextUtils.isEmpty(details.getReasonHeaderText())) { 187 mReasonHeaderText = Optional.empty(); 188 } else { 189 mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText()); 190 } 191 192 mSipDetails = Optional.ofNullable(details); 193 } 194 195 // Get the sip code of the network response. getNetworkRespSipCode()196 public synchronized Optional<Integer> getNetworkRespSipCode() { 197 return mNetworkRespSipCode; 198 } 199 200 // Get the reason of the network response. getReasonPhrase()201 public synchronized Optional<String> getReasonPhrase() { 202 return mReasonPhrase; 203 } 204 205 // Get the response sip code from the reason header. getReasonHeaderCause()206 public synchronized Optional<Integer> getReasonHeaderCause() { 207 return mReasonHeaderCause; 208 } 209 210 // Get the response phrae from the reason header. getReasonHeaderText()211 public synchronized Optional<String> getReasonHeaderText() { 212 return mReasonHeaderText; 213 } 214 getResponseSipCode()215 public Optional<Integer> getResponseSipCode() { 216 if (mReasonHeaderCause.isPresent()) { 217 return mReasonHeaderCause; 218 } else { 219 return mNetworkRespSipCode; 220 } 221 } 222 getResponseReason()223 public Optional<String> getResponseReason() { 224 if (mReasonPhrase.isPresent()) { 225 return mReasonPhrase; 226 } else { 227 return mReasonHeaderText; 228 } 229 } 230 231 /** 232 * Set the reason and retry-after info when the callback onTerminated is called. 233 * @param reason The reason why this request is terminated. 234 * @param retryAfterMillis How long to wait before retry this request. 235 */ setTerminated(String reason, long retryAfterMillis)236 public synchronized void setTerminated(String reason, long retryAfterMillis) { 237 mTerminatedReason = Optional.ofNullable(reason); 238 mRetryAfterMillis = Optional.of(retryAfterMillis); 239 } 240 241 /** 242 * @return The reason of terminating the subscription request. empty string if it has not 243 * been given. 244 */ getTerminatedReason()245 public synchronized String getTerminatedReason() { 246 return mTerminatedReason.orElse(""); 247 } 248 249 /** 250 * @return Return the retryAfterMillis, 0L if the value is not present. 251 */ getRetryAfterMillis()252 public synchronized long getRetryAfterMillis() { 253 return mRetryAfterMillis.orElse(0L); 254 } 255 256 /** 257 * Retrieve the SIP information which received from the network. 258 */ getSipDetails()259 public Optional<SipDetails> getSipDetails() { 260 return mSipDetails; 261 } 262 263 /** 264 * Add the capabilities which are retrieved from the cache. 265 */ addCachedCapabilities(List<RcsContactUceCapability> capabilityList)266 public synchronized void addCachedCapabilities(List<RcsContactUceCapability> capabilityList) { 267 mCachedCapabilityList.addAll(capabilityList); 268 269 // Update the flag to indicate that these contacts have received the capabilities updated. 270 updateCapsReceivedFlag(capabilityList); 271 } 272 273 /** 274 * Update the flag to indicate that the given contacts have received the capabilities updated. 275 */ updateCapsReceivedFlag(List<RcsContactUceCapability> updatedCapList)276 private synchronized void updateCapsReceivedFlag(List<RcsContactUceCapability> updatedCapList) { 277 for (RcsContactUceCapability updatedCap : updatedCapList) { 278 Uri updatedUri = updatedCap.getContactUri(); 279 if (updatedUri == null) continue; 280 String updatedUriStr = updatedUri.toString(); 281 282 for (Map.Entry<Uri, Boolean> contactCapEntry : mContactCapsReceived.entrySet()) { 283 String number = UceUtils.getContactNumber(contactCapEntry.getKey()); 284 if (!TextUtils.isEmpty(number) && updatedUriStr.contains(number)) { 285 // Set the flag that this contact has received the capability updated. 286 contactCapEntry.setValue(true); 287 } 288 } 289 } 290 } 291 292 /** 293 * Clear the cached capabilities when the cached capabilities have been sent to client. 294 */ removeCachedContactCapabilities()295 public synchronized void removeCachedContactCapabilities() { 296 mCachedCapabilityList.clear(); 297 } 298 299 /** 300 * @return the cached capabilities. 301 */ getCachedContactCapability()302 public synchronized List<RcsContactUceCapability> getCachedContactCapability() { 303 return Collections.unmodifiableList(mCachedCapabilityList); 304 } 305 306 /** 307 * Add the updated contact capabilities which sent from ImsService. 308 */ addUpdatedCapabilities(List<RcsContactUceCapability> capabilityList)309 public synchronized void addUpdatedCapabilities(List<RcsContactUceCapability> capabilityList) { 310 mUpdatedCapabilityList.addAll(capabilityList); 311 312 // Update the flag to indicate that these contacts have received the capabilities updated. 313 updateCapsReceivedFlag(capabilityList); 314 } 315 316 /** 317 * Remove the given capabilities from the UpdatedCapabilityList when these capabilities have 318 * updated to the requester. 319 */ removeUpdatedCapabilities(List<RcsContactUceCapability> capList)320 public synchronized void removeUpdatedCapabilities(List<RcsContactUceCapability> capList) { 321 mUpdatedCapabilityList.removeAll(capList); 322 } 323 324 /** 325 * Get all the updated capabilities to trigger the capability receive callback. 326 */ getUpdatedContactCapability()327 public synchronized List<RcsContactUceCapability> getUpdatedContactCapability() { 328 return Collections.unmodifiableList(mUpdatedCapabilityList); 329 } 330 331 /** 332 * Add the terminated resources which sent from ImsService. 333 */ addTerminatedResource(List<RcsContactTerminatedReason> resourceList)334 public synchronized void addTerminatedResource(List<RcsContactTerminatedReason> resourceList) { 335 // Convert the RcsContactTerminatedReason to RcsContactUceCapability 336 List<RcsContactUceCapability> capabilityList = resourceList.stream() 337 .filter(Objects::nonNull) 338 .map(reason -> PidfParserUtils.getTerminatedCapability( 339 reason.getContactUri(), reason.getReason())).collect(Collectors.toList()); 340 341 // Save the terminated resource. 342 mTerminatedResource.addAll(capabilityList); 343 344 // Update the flag to indicate that these contacts have received the capabilities updated. 345 updateCapsReceivedFlag(capabilityList); 346 } 347 348 /* 349 * Remove the given capabilities from the mTerminatedResource when these capabilities have 350 * updated to the requester. 351 */ removeTerminatedResources(List<RcsContactUceCapability> resourceList)352 public synchronized void removeTerminatedResources(List<RcsContactUceCapability> resourceList) { 353 mTerminatedResource.removeAll(resourceList); 354 } 355 356 /** 357 * Get the terminated resources which sent from ImsService. 358 */ getTerminatedResources()359 public synchronized List<RcsContactUceCapability> getTerminatedResources() { 360 return Collections.unmodifiableList(mTerminatedResource); 361 } 362 363 /** 364 * Set the remote's capabilities which are sent from the network. 365 */ setRemoteCapabilities(Set<String> remoteCaps)366 public synchronized void setRemoteCapabilities(Set<String> remoteCaps) { 367 if (remoteCaps != null) { 368 remoteCaps.stream().filter(Objects::nonNull).forEach(capability -> 369 mRemoteCaps.add(capability)); 370 } 371 } 372 373 /** 374 * Get the remote capability feature tags. 375 */ getRemoteCapability()376 public synchronized Set<String> getRemoteCapability() { 377 return Collections.unmodifiableSet(mRemoteCaps); 378 } 379 380 /** 381 * Check if the network response is success. 382 * @return true if the network response code is OK or Accepted and the Reason header cause 383 * is either not present or OK. 384 */ isNetworkResponseOK()385 public synchronized boolean isNetworkResponseOK() { 386 final int sipCodeOk = NetworkSipCode.SIP_CODE_OK; 387 final int sipCodeAccepted = NetworkSipCode.SIP_CODE_ACCEPTED; 388 Optional<Integer> respSipCode = getNetworkRespSipCode(); 389 if (respSipCode.filter(c -> (c == sipCodeOk || c == sipCodeAccepted)).isPresent() 390 && (!getReasonHeaderCause().isPresent() 391 || getReasonHeaderCause().filter(c -> c == sipCodeOk).isPresent())) { 392 return true; 393 } 394 return false; 395 } 396 397 /** 398 * Check whether the request is forbidden or not. 399 * @return true if the Reason header sip code is 403(Forbidden) or the response sip code is 403. 400 */ isRequestForbidden()401 public synchronized boolean isRequestForbidden() { 402 final int sipCodeForbidden = NetworkSipCode.SIP_CODE_FORBIDDEN; 403 if (getReasonHeaderCause().isPresent()) { 404 return getReasonHeaderCause().filter(c -> c == sipCodeForbidden).isPresent(); 405 } else { 406 return getNetworkRespSipCode().filter(c -> c == sipCodeForbidden).isPresent(); 407 } 408 } 409 410 /** 411 * Check the contacts of the request is not found. 412 * @return true if the sip code of the network response is one of NOT_FOUND(404), 413 * SIP_CODE_METHOD_NOT_ALLOWED(405) or DOES_NOT_EXIST_ANYWHERE(604) 414 */ isNotFound()415 public synchronized boolean isNotFound() { 416 Optional<Integer> respSipCode = Optional.empty(); 417 if (getReasonHeaderCause().isPresent()) { 418 respSipCode = getReasonHeaderCause(); 419 } else if (getNetworkRespSipCode().isPresent()) { 420 respSipCode = getNetworkRespSipCode(); 421 } 422 423 if (respSipCode.isPresent()) { 424 int sipCode = respSipCode.get(); 425 if (sipCode == NetworkSipCode.SIP_CODE_NOT_FOUND || 426 sipCode == NetworkSipCode.SIP_CODE_METHOD_NOT_ALLOWED || 427 sipCode == NetworkSipCode.SIP_CODE_DOES_NOT_EXIST_ANYWHERE) { 428 return true; 429 } 430 } 431 return false; 432 } 433 434 /** 435 * This method convert from the command error code which are defined in the 436 * RcsCapabilityExchangeImplBase to the Capabilities error code which are defined in the 437 * RcsUceAdapter. 438 */ getCapabilityErrorFromCommandError(@ommandCode int cmdError)439 public static int getCapabilityErrorFromCommandError(@CommandCode int cmdError) { 440 int uceError; 441 switch (cmdError) { 442 case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNKNOWN: 443 case RcsCapabilityExchangeImplBase.COMMAND_CODE_GENERIC_FAILURE: 444 case RcsCapabilityExchangeImplBase.COMMAND_CODE_INVALID_PARAM: 445 case RcsCapabilityExchangeImplBase.COMMAND_CODE_FETCH_ERROR: 446 case RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_SUPPORTED: 447 case RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE: 448 uceError = RcsUceAdapter.ERROR_GENERIC_FAILURE; 449 break; 450 case RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_FOUND: 451 uceError = RcsUceAdapter.ERROR_NOT_FOUND; 452 break; 453 case RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT: 454 uceError = RcsUceAdapter.ERROR_REQUEST_TIMEOUT; 455 break; 456 case RcsCapabilityExchangeImplBase.COMMAND_CODE_INSUFFICIENT_MEMORY: 457 uceError = RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY; 458 break; 459 case RcsCapabilityExchangeImplBase.COMMAND_CODE_LOST_NETWORK_CONNECTION: 460 uceError = RcsUceAdapter.ERROR_LOST_NETWORK; 461 break; 462 case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNAVAILABLE: 463 uceError = RcsUceAdapter.ERROR_SERVER_UNAVAILABLE; 464 break; 465 default: 466 uceError = RcsUceAdapter.ERROR_GENERIC_FAILURE; 467 break; 468 } 469 return uceError; 470 } 471 472 /** 473 * Convert the SIP error code which sent by ImsService to the capability error code. 474 */ getCapabilityErrorFromSipCode(CapabilityRequestResponse response)475 public static int getCapabilityErrorFromSipCode(CapabilityRequestResponse response) { 476 int sipError; 477 String respReason; 478 // Check the sip code in the Reason header first if the Reason Header is present. 479 if (response.getReasonHeaderCause().isPresent()) { 480 sipError = response.getReasonHeaderCause().get(); 481 respReason = response.getReasonHeaderText().orElse(""); 482 } else { 483 sipError = response.getNetworkRespSipCode().orElse(-1); 484 respReason = response.getReasonPhrase().orElse(""); 485 } 486 return NetworkSipCode.getCapabilityErrorFromSipCode(sipError, respReason, 487 UceController.REQUEST_TYPE_CAPABILITY); 488 } 489 490 @Override toString()491 public synchronized String toString() { 492 StringBuilder builder = new StringBuilder(); 493 return builder.append("RequestInternalError=").append(mRequestInternalError.orElse(-1)) 494 .append(", CommandErrorCode=").append(mCommandError.orElse(-1)) 495 .append(", NetworkResponseCode=").append(mNetworkRespSipCode.orElse(-1)) 496 .append(", NetworkResponseReason=").append(mReasonPhrase.orElse("")) 497 .append(", ReasonHeaderCause=").append(mReasonHeaderCause.orElse(-1)) 498 .append(", ReasonHeaderText=").append(mReasonHeaderText.orElse("")) 499 .append(", TerminatedReason=").append(mTerminatedReason.orElse("")) 500 .append(", RetryAfterMillis=").append(mRetryAfterMillis.orElse(0L)) 501 .append(", Terminated resource size=" + mTerminatedResource.size()) 502 .append(", cached capability size=" + mCachedCapabilityList.size()) 503 .append(", Updated capability size=" + mUpdatedCapabilityList.size()) 504 .append(", RemoteCaps size=" + mRemoteCaps.size()) 505 .toString(); 506 } 507 } 508