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