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