| /* |
| * 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.request; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.net.Uri; |
| import android.telephony.ims.RcsContactTerminatedReason; |
| import android.telephony.ims.RcsContactUceCapability; |
| import android.telephony.ims.RcsUceAdapter; |
| import android.telephony.ims.RcsUceAdapter.ErrorCode; |
| import android.telephony.ims.SipDetails; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase; |
| import android.telephony.ims.stub.RcsCapabilityExchangeImplBase.CommandCode; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.ims.rcs.uce.UceController; |
| import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils; |
| import com.android.ims.rcs.uce.util.NetworkSipCode; |
| import com.android.ims.rcs.uce.util.UceUtils; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * The container of the result of the capabilities request. |
| */ |
| public class CapabilityRequestResponse { |
| |
| private static final String LOG_TAG = UceUtils.getLogPrefix() + "CapabilityRequestResp"; |
| |
| // The error code when the request encounters internal errors. |
| private @ErrorCode Optional<Integer> mRequestInternalError; |
| |
| // The command error code of the request. It is assigned by the callback "onCommandError" |
| private @CommandCode Optional<Integer> mCommandError; |
| |
| // The SIP code and reason of the network response. |
| private Optional<Integer> mNetworkRespSipCode; |
| private Optional<String> mReasonPhrase; |
| |
| // The SIP code and the phrase read from the reason header |
| private Optional<Integer> mReasonHeaderCause; |
| private Optional<String> mReasonHeaderText; |
| |
| // The reason why the this request was terminated and how long after it can be retried. |
| // This value is assigned by the callback "onTerminated" |
| private Optional<String> mTerminatedReason; |
| private Optional<Long> mRetryAfterMillis; |
| |
| // The list of the valid capabilities which is retrieved from the cache. |
| private List<RcsContactUceCapability> mCachedCapabilityList; |
| |
| // The list of the updated capabilities. This is assigned by the callback |
| // "onNotifyCapabilitiesUpdate" |
| private List<RcsContactUceCapability> mUpdatedCapabilityList; |
| |
| // The list of the terminated resource. This is assigned by the callback |
| // "onResourceTerminated" |
| private List<RcsContactUceCapability> mTerminatedResource; |
| |
| // The list of the remote contact's capability. |
| private Set<String> mRemoteCaps; |
| |
| // The collection to record whether the request contacts have received the capabilities updated. |
| private Map<Uri, Boolean> mContactCapsReceived; |
| |
| // The SIP detail information of the network response. |
| private Optional<SipDetails> mSipDetails; |
| |
| public CapabilityRequestResponse() { |
| mRequestInternalError = Optional.empty(); |
| mCommandError = Optional.empty(); |
| mNetworkRespSipCode = Optional.empty(); |
| mReasonPhrase = Optional.empty(); |
| mReasonHeaderCause = Optional.empty(); |
| mReasonHeaderText = Optional.empty(); |
| mTerminatedReason = Optional.empty(); |
| mRetryAfterMillis = Optional.of(0L); |
| mTerminatedResource = new ArrayList<>(); |
| mCachedCapabilityList = new ArrayList<>(); |
| mUpdatedCapabilityList = new ArrayList<>(); |
| mRemoteCaps = new HashSet<>(); |
| mContactCapsReceived = new HashMap<>(); |
| mSipDetails = Optional.empty(); |
| } |
| |
| /** |
| * Set the request contacts which is expected to receive the capabilities updated. |
| */ |
| public synchronized void setRequestContacts(List<Uri> contactUris) { |
| // Initialize the default value to FALSE. All the numbers have not received the |
| // capabilities updated. |
| contactUris.forEach(contact -> mContactCapsReceived.put(contact, Boolean.FALSE)); |
| Log.d(LOG_TAG, "setRequestContacts: size=" + mContactCapsReceived.size()); |
| } |
| |
| /** |
| * Get the contacts that have not received the capabilities updated yet. |
| */ |
| public synchronized List<Uri> getNotReceiveCapabilityUpdatedContact() { |
| return mContactCapsReceived.entrySet() |
| .stream() |
| .filter(entry -> Objects.equals(entry.getValue(), Boolean.FALSE)) |
| .map(Map.Entry::getKey) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Set the request contacts which is expected to receive the capabilities updated. |
| */ |
| public synchronized boolean haveAllRequestCapsUpdatedBeenReceived() { |
| return !(mContactCapsReceived.containsValue(Boolean.FALSE)); |
| } |
| |
| /** |
| * Set the error code when the request encounters internal unexpected errors. |
| * @param errorCode the error code of the internal request error. |
| */ |
| public synchronized void setRequestInternalError(@ErrorCode int errorCode) { |
| mRequestInternalError = Optional.of(errorCode); |
| } |
| |
| /** |
| * Get the request internal error code. |
| */ |
| public synchronized Optional<Integer> getRequestInternalError() { |
| return mRequestInternalError; |
| } |
| |
| /** |
| * Set the command error code which is sent from ImsService and set the capability error code. |
| */ |
| public synchronized void setCommandError(@CommandCode int commandError) { |
| mCommandError = Optional.of(commandError); |
| } |
| |
| /** |
| * Get the command error codeof this request. |
| */ |
| public synchronized Optional<Integer> getCommandError() { |
| return mCommandError; |
| } |
| |
| /** |
| * Set the network response of this request which is sent by the network. |
| */ |
| public synchronized void setNetworkResponseCode(int sipCode, String reason) { |
| mNetworkRespSipCode = Optional.of(sipCode); |
| mReasonPhrase = Optional.ofNullable(reason); |
| } |
| |
| /** |
| * Set the network response of this request which is sent by the network. |
| */ |
| public synchronized void setSipDetails(@NonNull SipDetails details) { |
| setNetworkResponseCode(details.getResponseCode(), details.getResponsePhrase()); |
| if (details.getReasonHeaderCause() != 0) { |
| mReasonHeaderCause = Optional.of(details.getReasonHeaderCause()); |
| } else { |
| mReasonHeaderCause = Optional.empty(); |
| } |
| if (TextUtils.isEmpty(details.getReasonHeaderText())) { |
| mReasonHeaderText = Optional.empty(); |
| } else { |
| mReasonHeaderText = Optional.ofNullable(details.getReasonHeaderText()); |
| } |
| |
| mSipDetails = Optional.ofNullable(details); |
| } |
| |
| // Get the sip code of the network response. |
| public synchronized Optional<Integer> getNetworkRespSipCode() { |
| return mNetworkRespSipCode; |
| } |
| |
| // Get the reason of the network response. |
| public synchronized Optional<String> getReasonPhrase() { |
| return mReasonPhrase; |
| } |
| |
| // Get the response sip code from the reason header. |
| public synchronized Optional<Integer> getReasonHeaderCause() { |
| return mReasonHeaderCause; |
| } |
| |
| // Get the response phrae from the reason header. |
| public synchronized Optional<String> getReasonHeaderText() { |
| return mReasonHeaderText; |
| } |
| |
| public Optional<Integer> getResponseSipCode() { |
| if (mReasonHeaderCause.isPresent()) { |
| return mReasonHeaderCause; |
| } else { |
| return mNetworkRespSipCode; |
| } |
| } |
| |
| public Optional<String> getResponseReason() { |
| if (mReasonPhrase.isPresent()) { |
| return mReasonPhrase; |
| } else { |
| return mReasonHeaderText; |
| } |
| } |
| |
| /** |
| * Set the reason and retry-after info when the callback onTerminated is called. |
| * @param reason The reason why this request is terminated. |
| * @param retryAfterMillis How long to wait before retry this request. |
| */ |
| public synchronized void setTerminated(String reason, long retryAfterMillis) { |
| mTerminatedReason = Optional.ofNullable(reason); |
| mRetryAfterMillis = Optional.of(retryAfterMillis); |
| } |
| |
| /** |
| * @return The reason of terminating the subscription request. empty string if it has not |
| * been given. |
| */ |
| public synchronized String getTerminatedReason() { |
| return mTerminatedReason.orElse(""); |
| } |
| |
| /** |
| * @return Return the retryAfterMillis, 0L if the value is not present. |
| */ |
| public synchronized long getRetryAfterMillis() { |
| return mRetryAfterMillis.orElse(0L); |
| } |
| |
| /** |
| * Retrieve the SIP information which received from the network. |
| */ |
| public Optional<SipDetails> getSipDetails() { |
| return mSipDetails; |
| } |
| |
| /** |
| * Add the capabilities which are retrieved from the cache. |
| */ |
| public synchronized void addCachedCapabilities(List<RcsContactUceCapability> capabilityList) { |
| mCachedCapabilityList.addAll(capabilityList); |
| |
| // Update the flag to indicate that these contacts have received the capabilities updated. |
| updateCapsReceivedFlag(capabilityList); |
| } |
| |
| /** |
| * Update the flag to indicate that the given contacts have received the capabilities updated. |
| */ |
| private synchronized void updateCapsReceivedFlag(List<RcsContactUceCapability> updatedCapList) { |
| for (RcsContactUceCapability updatedCap : updatedCapList) { |
| Uri updatedUri = updatedCap.getContactUri(); |
| if (updatedUri == null) continue; |
| String updatedUriStr = updatedUri.toString(); |
| |
| for (Map.Entry<Uri, Boolean> contactCapEntry : mContactCapsReceived.entrySet()) { |
| String number = UceUtils.getContactNumber(contactCapEntry.getKey()); |
| if (!TextUtils.isEmpty(number) && updatedUriStr.contains(number)) { |
| // Set the flag that this contact has received the capability updated. |
| contactCapEntry.setValue(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Clear the cached capabilities when the cached capabilities have been sent to client. |
| */ |
| public synchronized void removeCachedContactCapabilities() { |
| mCachedCapabilityList.clear(); |
| } |
| |
| /** |
| * @return the cached capabilities. |
| */ |
| public synchronized List<RcsContactUceCapability> getCachedContactCapability() { |
| return Collections.unmodifiableList(mCachedCapabilityList); |
| } |
| |
| /** |
| * Add the updated contact capabilities which sent from ImsService. |
| */ |
| public synchronized void addUpdatedCapabilities(List<RcsContactUceCapability> capabilityList) { |
| mUpdatedCapabilityList.addAll(capabilityList); |
| |
| // Update the flag to indicate that these contacts have received the capabilities updated. |
| updateCapsReceivedFlag(capabilityList); |
| } |
| |
| /** |
| * Remove the given capabilities from the UpdatedCapabilityList when these capabilities have |
| * updated to the requester. |
| */ |
| public synchronized void removeUpdatedCapabilities(List<RcsContactUceCapability> capList) { |
| mUpdatedCapabilityList.removeAll(capList); |
| } |
| |
| /** |
| * Get all the updated capabilities to trigger the capability receive callback. |
| */ |
| public synchronized List<RcsContactUceCapability> getUpdatedContactCapability() { |
| return Collections.unmodifiableList(mUpdatedCapabilityList); |
| } |
| |
| /** |
| * Add the terminated resources which sent from ImsService. |
| */ |
| public synchronized void addTerminatedResource(List<RcsContactTerminatedReason> resourceList) { |
| // Convert the RcsContactTerminatedReason to RcsContactUceCapability |
| List<RcsContactUceCapability> capabilityList = resourceList.stream() |
| .filter(Objects::nonNull) |
| .map(reason -> PidfParserUtils.getTerminatedCapability( |
| reason.getContactUri(), reason.getReason())).collect(Collectors.toList()); |
| |
| // Save the terminated resource. |
| mTerminatedResource.addAll(capabilityList); |
| |
| // Update the flag to indicate that these contacts have received the capabilities updated. |
| updateCapsReceivedFlag(capabilityList); |
| } |
| |
| /* |
| * Remove the given capabilities from the mTerminatedResource when these capabilities have |
| * updated to the requester. |
| */ |
| public synchronized void removeTerminatedResources(List<RcsContactUceCapability> resourceList) { |
| mTerminatedResource.removeAll(resourceList); |
| } |
| |
| /** |
| * Get the terminated resources which sent from ImsService. |
| */ |
| public synchronized List<RcsContactUceCapability> getTerminatedResources() { |
| return Collections.unmodifiableList(mTerminatedResource); |
| } |
| |
| /** |
| * Set the remote's capabilities which are sent from the network. |
| */ |
| public synchronized void setRemoteCapabilities(Set<String> remoteCaps) { |
| if (remoteCaps != null) { |
| remoteCaps.stream().filter(Objects::nonNull).forEach(capability -> |
| mRemoteCaps.add(capability)); |
| } |
| } |
| |
| /** |
| * Get the remote capability feature tags. |
| */ |
| public synchronized Set<String> getRemoteCapability() { |
| return Collections.unmodifiableSet(mRemoteCaps); |
| } |
| |
| /** |
| * Check if the network response is success. |
| * @return true if the network response code is OK or Accepted and the Reason header cause |
| * is either not present or OK. |
| */ |
| public synchronized boolean isNetworkResponseOK() { |
| final int sipCodeOk = NetworkSipCode.SIP_CODE_OK; |
| final int sipCodeAccepted = NetworkSipCode.SIP_CODE_ACCEPTED; |
| Optional<Integer> respSipCode = getNetworkRespSipCode(); |
| if (respSipCode.filter(c -> (c == sipCodeOk || c == sipCodeAccepted)).isPresent() |
| && (!getReasonHeaderCause().isPresent() |
| || getReasonHeaderCause().filter(c -> c == sipCodeOk).isPresent())) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Check whether the request is forbidden or not. |
| * @return true if the Reason header sip code is 403(Forbidden) or the response sip code is 403. |
| */ |
| public synchronized boolean isRequestForbidden() { |
| final int sipCodeForbidden = NetworkSipCode.SIP_CODE_FORBIDDEN; |
| if (getReasonHeaderCause().isPresent()) { |
| return getReasonHeaderCause().filter(c -> c == sipCodeForbidden).isPresent(); |
| } else { |
| return getNetworkRespSipCode().filter(c -> c == sipCodeForbidden).isPresent(); |
| } |
| } |
| |
| /** |
| * Check the contacts of the request is not found. |
| * @return true if the sip code of the network response is one of NOT_FOUND(404), |
| * SIP_CODE_METHOD_NOT_ALLOWED(405) or DOES_NOT_EXIST_ANYWHERE(604) |
| */ |
| public synchronized boolean isNotFound() { |
| Optional<Integer> respSipCode = Optional.empty(); |
| if (getReasonHeaderCause().isPresent()) { |
| respSipCode = getReasonHeaderCause(); |
| } else if (getNetworkRespSipCode().isPresent()) { |
| respSipCode = getNetworkRespSipCode(); |
| } |
| |
| if (respSipCode.isPresent()) { |
| int sipCode = respSipCode.get(); |
| if (sipCode == NetworkSipCode.SIP_CODE_NOT_FOUND || |
| sipCode == NetworkSipCode.SIP_CODE_METHOD_NOT_ALLOWED || |
| sipCode == NetworkSipCode.SIP_CODE_DOES_NOT_EXIST_ANYWHERE) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * This method convert from the command error code which are defined in the |
| * RcsCapabilityExchangeImplBase to the Capabilities error code which are defined in the |
| * RcsUceAdapter. |
| */ |
| public static int getCapabilityErrorFromCommandError(@CommandCode int cmdError) { |
| int uceError; |
| switch (cmdError) { |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNKNOWN: |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_GENERIC_FAILURE: |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_INVALID_PARAM: |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_FETCH_ERROR: |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_SUPPORTED: |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_NO_CHANGE: |
| uceError = RcsUceAdapter.ERROR_GENERIC_FAILURE; |
| break; |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_NOT_FOUND: |
| uceError = RcsUceAdapter.ERROR_NOT_FOUND; |
| break; |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_REQUEST_TIMEOUT: |
| uceError = RcsUceAdapter.ERROR_REQUEST_TIMEOUT; |
| break; |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_INSUFFICIENT_MEMORY: |
| uceError = RcsUceAdapter.ERROR_INSUFFICIENT_MEMORY; |
| break; |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_LOST_NETWORK_CONNECTION: |
| uceError = RcsUceAdapter.ERROR_LOST_NETWORK; |
| break; |
| case RcsCapabilityExchangeImplBase.COMMAND_CODE_SERVICE_UNAVAILABLE: |
| uceError = RcsUceAdapter.ERROR_SERVER_UNAVAILABLE; |
| break; |
| default: |
| uceError = RcsUceAdapter.ERROR_GENERIC_FAILURE; |
| break; |
| } |
| return uceError; |
| } |
| |
| /** |
| * Convert the SIP error code which sent by ImsService to the capability error code. |
| */ |
| public static int getCapabilityErrorFromSipCode(CapabilityRequestResponse response) { |
| int sipError; |
| String respReason; |
| // Check the sip code in the Reason header first if the Reason Header is present. |
| if (response.getReasonHeaderCause().isPresent()) { |
| sipError = response.getReasonHeaderCause().get(); |
| respReason = response.getReasonHeaderText().orElse(""); |
| } else { |
| sipError = response.getNetworkRespSipCode().orElse(-1); |
| respReason = response.getReasonPhrase().orElse(""); |
| } |
| return NetworkSipCode.getCapabilityErrorFromSipCode(sipError, respReason, |
| UceController.REQUEST_TYPE_CAPABILITY); |
| } |
| |
| @Override |
| public synchronized String toString() { |
| StringBuilder builder = new StringBuilder(); |
| return builder.append("RequestInternalError=").append(mRequestInternalError.orElse(-1)) |
| .append(", CommandErrorCode=").append(mCommandError.orElse(-1)) |
| .append(", NetworkResponseCode=").append(mNetworkRespSipCode.orElse(-1)) |
| .append(", NetworkResponseReason=").append(mReasonPhrase.orElse("")) |
| .append(", ReasonHeaderCause=").append(mReasonHeaderCause.orElse(-1)) |
| .append(", ReasonHeaderText=").append(mReasonHeaderText.orElse("")) |
| .append(", TerminatedReason=").append(mTerminatedReason.orElse("")) |
| .append(", RetryAfterMillis=").append(mRetryAfterMillis.orElse(0L)) |
| .append(", Terminated resource size=" + mTerminatedResource.size()) |
| .append(", cached capability size=" + mCachedCapabilityList.size()) |
| .append(", Updated capability size=" + mUpdatedCapabilityList.size()) |
| .append(", RemoteCaps size=" + mRemoteCaps.size()) |
| .toString(); |
| } |
| } |