• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 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 static android.telephony.ims.stub.RcsCapabilityExchangeImplBase.COMMAND_CODE_GENERIC_FAILURE;
20 
21 import android.net.Uri;
22 import android.os.RemoteException;
23 import android.telephony.ims.RcsContactUceCapability;
24 import android.telephony.ims.RcsUceAdapter;
25 import android.telephony.ims.aidl.IRcsUceControllerCallback;
26 
27 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult;
28 import com.android.ims.rcs.uce.eab.EabCapabilityResult;
29 import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils;
30 import com.android.ims.rcs.uce.request.SubscriptionTerminatedHelper.TerminatedResult;
31 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
32 import com.android.ims.rcs.uce.UceStatsWriter;
33 import com.android.internal.annotations.VisibleForTesting;
34 
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Comparator;
38 import java.util.List;
39 import java.util.Optional;
40 import java.util.stream.Collectors;
41 
42 /**
43  * Responsible for the communication and interaction between SubscribeRequests and triggering
44  * the callback to notify the result of the capabilities request.
45  */
46 public class SubscribeRequestCoordinator extends UceRequestCoordinator {
47     /**
48      * The builder of the SubscribeRequestCoordinator.
49      */
50     public static final class Builder {
51         private SubscribeRequestCoordinator mRequestCoordinator;
52 
53         /**
54          * The builder of the SubscribeRequestCoordinator class.
55          */
Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c)56         public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c) {
57             mRequestCoordinator = new SubscribeRequestCoordinator(subId, requests, c,
58                     UceStatsWriter.getInstance());
59         }
60         @VisibleForTesting
Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c, UceStatsWriter instance)61         public Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback c,
62                 UceStatsWriter instance) {
63             mRequestCoordinator = new SubscribeRequestCoordinator(subId, requests, c, instance);
64         }
65 
66         /**
67          * Set the callback to receive the request updated.
68          */
setCapabilitiesCallback(IRcsUceControllerCallback callback)69         public Builder setCapabilitiesCallback(IRcsUceControllerCallback callback) {
70             mRequestCoordinator.setCapabilitiesCallback(callback);
71             return this;
72         }
73 
74         /**
75          * Get the SubscribeRequestCoordinator instance.
76          */
build()77         public SubscribeRequestCoordinator build() {
78             return mRequestCoordinator;
79         }
80     }
81 
82     /**
83      * Different request updated events will create different {@link RequestResult}. Define the
84      * interface to get the {@link RequestResult} instance according to the given task ID and
85      * {@link CapabilityRequestResponse}.
86      */
87     @FunctionalInterface
88     private interface RequestResultCreator {
createRequestResult(long taskId, CapabilityRequestResponse response, RequestManagerCallback requestMgrCallback)89         RequestResult createRequestResult(long taskId, CapabilityRequestResponse response,
90                 RequestManagerCallback requestMgrCallback);
91     }
92 
93     // The RequestResult creator of the request error.
94     private static final RequestResultCreator sRequestErrorCreator = (taskId, response,
95             requestMgrCallback) -> {
96         int errorCode = response.getRequestInternalError().orElse(DEFAULT_ERROR_CODE);
97         long retryAfter = response.getRetryAfterMillis();
98         return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
99     };
100 
101     // The RequestResult creator of the command error.
102     private static final RequestResultCreator sCommandErrorCreator = (taskId, response,
103             requestMgrCallback) -> {
104         int cmdError = response.getCommandError().orElse(COMMAND_CODE_GENERIC_FAILURE);
105         int errorCode = CapabilityRequestResponse.getCapabilityErrorFromCommandError(cmdError);
106         long retryAfter = response.getRetryAfterMillis();
107         return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
108     };
109 
110     // The RequestResult creator of the network response error.
111     private static final RequestResultCreator sNetworkRespErrorCreator = (taskId, response,
112             requestMgrCallback) -> {
113         DeviceStateResult deviceState = requestMgrCallback.getDeviceState();
114         if (deviceState.isRequestForbidden()) {
115             int errorCode = deviceState.getErrorCode().orElse(RcsUceAdapter.ERROR_FORBIDDEN);
116             long retryAfter = deviceState.getRequestRetryAfterMillis();
117             return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
118         } else {
119             int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
120             long retryAfter = response.getRetryAfterMillis();
121             return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
122         }
123     };
124 
125     // The RequestResult creator of the network response is not 200 OK, however, we can to treat
126     // it as a successful result and finish the request
127     private static final RequestResultCreator sNetworkRespSuccessfulCreator = (taskId, response,
128             requestMgrCallback) -> RequestResult.createSuccessResult(taskId);
129 
130     // The RequestResult creator of the request terminated.
131     private static final RequestResultCreator sTerminatedCreator = (taskId, response,
132             requestMgrCallback) -> {
133         // Check the given terminated reason to determine whether clients should retry or not.
134         TerminatedResult terminatedResult = SubscriptionTerminatedHelper.getAnalysisResult(
135                 response.getTerminatedReason(), response.getRetryAfterMillis(),
136                 response.haveAllRequestCapsUpdatedBeenReceived());
137         if (terminatedResult.getErrorCode().isPresent()) {
138             // If the terminated error code is present, it means that the request is failed.
139             int errorCode = terminatedResult.getErrorCode().get();
140             long terminatedRetry = terminatedResult.getRetryAfterMillis();
141             return RequestResult.createFailedResult(taskId, errorCode, terminatedRetry);
142         } else if (!response.isNetworkResponseOK() || response.getRetryAfterMillis() > 0L) {
143             // If the network response is failed or the retryAfter is not 0, this request is failed.
144             long retryAfterMillis = response.getRetryAfterMillis();
145             int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
146             return RequestResult.createFailedResult(taskId, errorCode, retryAfterMillis);
147         } else {
148             return RequestResult.createSuccessResult(taskId);
149         }
150     };
151 
152     // The RequestResult creator for does not need to request from the network.
153     private static final RequestResultCreator sNotNeedRequestFromNetworkCreator =
154             (taskId, response, requestMgrCallback) -> RequestResult.createSuccessResult(taskId);
155 
156     // The RequestResult creator of the request timeout.
157     private static final RequestResultCreator sRequestTimeoutCreator =
158             (taskId, response, requestMgrCallback) -> RequestResult.createFailedResult(taskId,
159                     RcsUceAdapter.ERROR_REQUEST_TIMEOUT, 0L);
160 
161     // The callback to notify the result of the capabilities request.
162     private volatile IRcsUceControllerCallback mCapabilitiesCallback;
163 
164     private final UceStatsWriter mUceStatsWriter;
165 
SubscribeRequestCoordinator(int subId, Collection<UceRequest> requests, RequestManagerCallback requestMgrCallback, UceStatsWriter instance)166     private SubscribeRequestCoordinator(int subId, Collection<UceRequest> requests,
167             RequestManagerCallback requestMgrCallback, UceStatsWriter instance) {
168         super(subId, requests, requestMgrCallback);
169         mUceStatsWriter = instance;
170         logd("SubscribeRequestCoordinator: created");
171     }
172 
setCapabilitiesCallback(IRcsUceControllerCallback callback)173     private void setCapabilitiesCallback(IRcsUceControllerCallback callback) {
174         mCapabilitiesCallback = callback;
175     }
176 
177     @Override
onFinish()178     public void onFinish() {
179         logd("SubscribeRequestCoordinator: onFinish");
180         mCapabilitiesCallback = null;
181         super.onFinish();
182     }
183 
184     @Override
onRequestUpdated(long taskId, @UceRequestUpdate int event)185     public void onRequestUpdated(long taskId, @UceRequestUpdate int event) {
186         if (mIsFinished) return;
187         SubscribeRequest request = (SubscribeRequest) getUceRequest(taskId);
188         if (request == null) {
189             logw("onRequestUpdated: Cannot find SubscribeRequest taskId=" + taskId);
190             return;
191         }
192 
193         logd("onRequestUpdated(SubscribeRequest): taskId=" + taskId + ", event=" +
194                 REQUEST_EVENT_DESC.get(event));
195 
196         switch (event) {
197             case REQUEST_UPDATE_ERROR:
198                 handleRequestError(request);
199                 break;
200             case REQUEST_UPDATE_COMMAND_ERROR:
201                 handleCommandError(request);
202                 break;
203             case REQUEST_UPDATE_NETWORK_RESPONSE:
204                 handleNetworkResponse(request);
205                 break;
206             case REQUEST_UPDATE_CAPABILITY_UPDATE:
207                 handleCapabilitiesUpdated(request);
208                 break;
209             case REQUEST_UPDATE_RESOURCE_TERMINATED:
210                 handleResourceTerminated(request);
211                 break;
212             case REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE:
213                 handleCachedCapabilityUpdated(request);
214                 break;
215             case REQUEST_UPDATE_TERMINATED:
216                 handleTerminated(request);
217                 break;
218             case REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK:
219                 handleNoNeedRequestFromNetwork(request);
220                 break;
221             case REQUEST_UPDATE_TIMEOUT:
222                 handleRequestTimeout(request);
223                 break;
224             default:
225                 logw("onRequestUpdated(SubscribeRequest): invalid event " + event);
226                 break;
227         }
228 
229         // End this instance if all the UceRequests in the coordinator are finished.
230         checkAndFinishRequestCoordinator();
231     }
232 
233     /**
234      * Finish the SubscribeRequest because it has encountered error.
235      */
handleRequestError(SubscribeRequest request)236     private void handleRequestError(SubscribeRequest request) {
237         CapabilityRequestResponse response = request.getRequestResponse();
238         logd("handleRequestError: " + request.toString());
239 
240         // Finish this request.
241         request.onFinish();
242 
243         // Remove this request from the activated collection and notify RequestManager.
244         Long taskId = request.getTaskId();
245         RequestResult requestResult = sRequestErrorCreator.createRequestResult(taskId, response,
246                 mRequestManagerCallback);
247         moveRequestToFinishedCollection(taskId, requestResult);
248     }
249 
250     /**
251      * This method is called when the given SubscribeRequest received the onCommandError callback
252      * from the ImsService.
253      */
handleCommandError(SubscribeRequest request)254     private void handleCommandError(SubscribeRequest request) {
255         CapabilityRequestResponse response = request.getRequestResponse();
256         logd("handleCommandError: " + request.toString());
257 
258         // Finish this request.
259         request.onFinish();
260 
261         int commandErrorCode = response.getCommandError().orElse(0);
262         mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.SUBSCRIBE_EVENT,
263             false, commandErrorCode, 0);
264 
265         // Remove this request from the activated collection and notify RequestManager.
266         Long taskId = request.getTaskId();
267         RequestResult requestResult = sCommandErrorCreator.createRequestResult(taskId, response,
268                 mRequestManagerCallback);
269         moveRequestToFinishedCollection(taskId, requestResult);
270     }
271 
272     /**
273      * This method is called when the given SubscribeRequest received the onNetworkResponse
274      * callback from the ImsService.
275      */
handleNetworkResponse(SubscribeRequest request)276     private void handleNetworkResponse(SubscribeRequest request) {
277         CapabilityRequestResponse response = request.getRequestResponse();
278         logd("handleNetworkResponse: " + response.toString());
279 
280         int respCode = response.getNetworkRespSipCode().orElse(0);
281         mUceStatsWriter.setSubscribeResponse(mSubId, request.getTaskId(), respCode);
282 
283         // Refresh the device state with the request result.
284         response.getResponseSipCode().ifPresent(sipCode -> {
285             String reason = response.getResponseReason().orElse("");
286             mRequestManagerCallback.refreshDeviceState(sipCode, reason);
287         });
288 
289         // When the network response is unsuccessful, there is no subsequent callback for this
290         // request. Check the forbidden state and finish this request. Otherwise, keep waiting for
291         // the subsequent callback of this request.
292         if (!response.isNetworkResponseOK()) {
293             // Handle the network response not OK cases and get the request result to finish this
294             // request.
295             RequestResult requestResult = handleNetworkResponseFailed(request);
296 
297             // Trigger capabilities updated callback if there is any.
298             List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
299             if (!updatedCapList.isEmpty()) {
300                 mRequestManagerCallback.saveCapabilities(updatedCapList);
301                 triggerCapabilitiesReceivedCallback(updatedCapList);
302                 response.removeUpdatedCapabilities(updatedCapList);
303             }
304 
305             // Finish this request.
306             request.onFinish();
307 
308             // Remove this request from the activated collection and notify RequestManager.
309             moveRequestToFinishedCollection(request.getTaskId(), requestResult);
310         }
311     }
312 
handleNetworkResponseFailed(SubscribeRequest request)313     private RequestResult handleNetworkResponseFailed(SubscribeRequest request) {
314         final long taskId = request.getTaskId();
315         final CapabilityRequestResponse response = request.getRequestResponse();
316         final List<Uri> requestUris = response.getNotReceiveCapabilityUpdatedContact();
317         RequestResult requestResult = null;
318 
319         if (response.isNotFound()) {
320             // In the network response with the not found case, we won't receive the capabilities
321             // updated callback from the ImsService afterward. Therefore, we create the capabilities
322             // with the result REQUEST_RESULT_NOT_FOUND by ourself and will trigger the
323             // capabilities received callback to the clients later.
324             List<RcsContactUceCapability> capabilityList = requestUris.stream().map(uri ->
325                     PidfParserUtils.getNotFoundContactCapabilities(uri))
326                     .collect(Collectors.toList());
327             response.addUpdatedCapabilities(capabilityList);
328 
329             // We treat the NOT FOUND is a successful result.
330             requestResult = sNetworkRespSuccessfulCreator.createRequestResult(taskId, response,
331                     mRequestManagerCallback);
332         } else {
333             // The request result is unsuccessful and it's not the NOT FOUND error. we need to get
334             // the capabilities from the cache.
335             List<RcsContactUceCapability> capabilitiesList =
336                     getCapabilitiesFromCacheIncludingExpired(requestUris);
337             response.addUpdatedCapabilities(capabilitiesList);
338 
339             // Add to the throttling list for the inconclusive result of the contacts.
340             mRequestManagerCallback.addToThrottlingList(requestUris,
341                     response.getResponseSipCode().orElse(
342                             com.android.ims.rcs.uce.util.NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT));
343 
344             requestResult = sNetworkRespErrorCreator.createRequestResult(taskId, response,
345                     mRequestManagerCallback);
346         }
347         return requestResult;
348     }
349 
350     /**
351      * Get the contact capabilities from the cache even if the capabilities have expired. If the
352      * capabilities doesn't exist, create the non-RCS capabilities instead.
353      * @param uris the uris to get the capabilities from cache.
354      * @return The contact capabilities for the given uris.
355      */
getCapabilitiesFromCacheIncludingExpired(List<Uri> uris)356     private List<RcsContactUceCapability> getCapabilitiesFromCacheIncludingExpired(List<Uri> uris) {
357         List<RcsContactUceCapability> resultList = new ArrayList<>();
358         List<RcsContactUceCapability> notFoundFromCacheList = new ArrayList<>();
359 
360         // Get the capabilities from the cache.
361         List<EabCapabilityResult> eabResultList =
362                 mRequestManagerCallback.getCapabilitiesFromCacheIncludingExpired(uris);
363 
364         eabResultList.forEach(eabResult -> {
365             if (eabResult.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL ||
366                 eabResult.getStatus() == EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE) {
367                 // The capabilities are found, add to the result list
368                 resultList.add(eabResult.getContactCapabilities());
369             } else {
370                 // Cannot get the capabilities from cache, create the non-RCS capabilities instead.
371                 notFoundFromCacheList.add(PidfParserUtils.getNotFoundContactCapabilities(
372                         eabResult.getContact()));
373             }
374         });
375 
376         if (!notFoundFromCacheList.isEmpty()) {
377             resultList.addAll(notFoundFromCacheList);
378         }
379 
380         logd("getCapabilitiesFromCacheIncludingExpired: requesting uris size=" + uris.size() +
381                 ", capabilities not found from cache size=" + notFoundFromCacheList.size());
382         return resultList;
383     }
384 
385     /**
386      * This method is called when the given SubscribeRequest received the onNotifyCapabilitiesUpdate
387      * callback from the ImsService.
388      */
handleCapabilitiesUpdated(SubscribeRequest request)389     private void handleCapabilitiesUpdated(SubscribeRequest request) {
390         CapabilityRequestResponse response = request.getRequestResponse();
391         Long taskId = request.getTaskId();
392         List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
393         logd("handleCapabilitiesUpdated: taskId=" + taskId + ", size=" + updatedCapList.size());
394 
395         if (updatedCapList.isEmpty()) {
396             return;
397         }
398 
399         mUceStatsWriter.setPresenceNotifyEvent(mSubId, taskId, updatedCapList);
400         // Save the updated capabilities to the cache.
401         mRequestManagerCallback.saveCapabilities(updatedCapList);
402 
403         // Trigger the capabilities updated callback and remove the given capabilities that have
404         // executed the callback onCapabilitiesReceived.
405         triggerCapabilitiesReceivedCallback(updatedCapList);
406         response.removeUpdatedCapabilities(updatedCapList);
407     }
408 
409     /**
410      * This method is called when the given SubscribeRequest received the onResourceTerminated
411      * callback from the ImsService.
412      */
handleResourceTerminated(SubscribeRequest request)413     private void handleResourceTerminated(SubscribeRequest request) {
414         CapabilityRequestResponse response = request.getRequestResponse();
415         Long taskId = request.getTaskId();
416         List<RcsContactUceCapability> terminatedResources = response.getTerminatedResources();
417         logd("handleResourceTerminated: taskId=" + taskId + ", size=" + terminatedResources.size());
418 
419         if (terminatedResources.isEmpty()) {
420             return;
421         }
422 
423         mUceStatsWriter.setPresenceNotifyEvent(mSubId, taskId, terminatedResources);
424 
425         // Save the terminated capabilities to the cache.
426         mRequestManagerCallback.saveCapabilities(terminatedResources);
427 
428         // Trigger the capabilities updated callback and remove the given capabilities from the
429         // resource terminated list.
430         triggerCapabilitiesReceivedCallback(terminatedResources);
431         response.removeTerminatedResources(terminatedResources);
432     }
433 
434     /**
435      * This method is called when the given SubscribeRequest retrieve the cached capabilities.
436      */
handleCachedCapabilityUpdated(SubscribeRequest request)437     private void handleCachedCapabilityUpdated(SubscribeRequest request) {
438         CapabilityRequestResponse response = request.getRequestResponse();
439         Long taskId = request.getTaskId();
440         List<RcsContactUceCapability> cachedCapList = response.getCachedContactCapability();
441         logd("handleCachedCapabilityUpdated: taskId=" + taskId + ", size=" + cachedCapList.size());
442 
443         if (cachedCapList.isEmpty()) {
444             return;
445         }
446 
447         // Trigger the capabilities updated callback.
448         triggerCapabilitiesReceivedCallback(cachedCapList);
449         response.removeCachedContactCapabilities();
450     }
451 
452     /**
453      * This method is called when the given SubscribeRequest received the onTerminated callback
454      * from the ImsService.
455      */
handleTerminated(SubscribeRequest request)456     private void handleTerminated(SubscribeRequest request) {
457         CapabilityRequestResponse response = request.getRequestResponse();
458         logd("handleTerminated: " + response.toString());
459 
460         // Finish this request.
461         request.onFinish();
462 
463         // Remove this request from the activated collection and notify RequestManager.
464         Long taskId = request.getTaskId();
465         mUceStatsWriter.setSubscribeTerminated(mSubId, taskId, response.getTerminatedReason());
466         RequestResult requestResult = sTerminatedCreator.createRequestResult(taskId, response,
467                 mRequestManagerCallback);
468         moveRequestToFinishedCollection(taskId, requestResult);
469     }
470 
471     /**
472      * This method is called when all the capabilities can be retrieved from the cached and it does
473      * not need to request capabilities from the network.
474      */
handleNoNeedRequestFromNetwork(SubscribeRequest request)475     private void handleNoNeedRequestFromNetwork(SubscribeRequest request) {
476         CapabilityRequestResponse response = request.getRequestResponse();
477         logd("handleNoNeedRequestFromNetwork: " + response.toString());
478 
479         // Finish this request.
480         request.onFinish();
481 
482         // Remove this request from the activated collection and notify RequestManager.
483         long taskId = request.getTaskId();
484         RequestResult requestResult = sNotNeedRequestFromNetworkCreator.createRequestResult(taskId,
485                 response, mRequestManagerCallback);
486         moveRequestToFinishedCollection(taskId, requestResult);
487     }
488 
489     /**
490      * This method is called when the framework did not receive the capabilities request result.
491      */
handleRequestTimeout(SubscribeRequest request)492     private void handleRequestTimeout(SubscribeRequest request) {
493         CapabilityRequestResponse response = request.getRequestResponse();
494         List<Uri> requestUris = response.getNotReceiveCapabilityUpdatedContact();
495         logd("handleRequestTimeout: " + response);
496         logd("handleRequestTimeout: not received updated uri size=" + requestUris.size());
497 
498         // Add to the throttling list for the inconclusive result of the contacts.
499         mRequestManagerCallback.addToThrottlingList(requestUris,
500                 com.android.ims.rcs.uce.util.NetworkSipCode.SIP_CODE_REQUEST_TIMEOUT);
501 
502         // Get the capabilities from the cache instead and add to the response.
503         List<RcsContactUceCapability> capabilitiesList =
504                 getCapabilitiesFromCacheIncludingExpired(requestUris);
505         response.addUpdatedCapabilities(capabilitiesList);
506 
507         // Trigger capabilities updated callback if there is any.
508         List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
509         if (!updatedCapList.isEmpty()) {
510             triggerCapabilitiesReceivedCallback(updatedCapList);
511             response.removeUpdatedCapabilities(updatedCapList);
512         }
513 
514         // Remove this request from the activated collection and notify RequestManager.
515         long taskId = request.getTaskId();
516         RequestResult requestResult = sRequestTimeoutCreator.createRequestResult(taskId,
517                 response, mRequestManagerCallback);
518 
519         // Finish this request
520         request.onFinish();
521 
522         // Remove this request from the activated collection and notify RequestManager.
523         moveRequestToFinishedCollection(taskId, requestResult);
524     }
525 
checkAndFinishRequestCoordinator()526     private void checkAndFinishRequestCoordinator() {
527         synchronized (mCollectionLock) {
528             // Return because there are requests running.
529             if (!mActivatedRequests.isEmpty()) {
530                 return;
531             }
532 
533             // All the requests has finished, find the request which has the max retryAfter time.
534             // If the result is empty, it means all the request are success.
535             Optional<RequestResult> optRequestResult =
536                     mFinishedRequests.values().stream()
537                         .filter(result -> !result.isRequestSuccess())
538                         .max(Comparator.comparingLong(result ->
539                                 result.getRetryMillis().orElse(-1L)));
540 
541             // Trigger the callback
542             if (optRequestResult.isPresent()) {
543                 RequestResult result = optRequestResult.get();
544                 int errorCode = result.getErrorCode().orElse(DEFAULT_ERROR_CODE);
545                 long retryAfter = result.getRetryMillis().orElse(0L);
546                 triggerErrorCallback(errorCode, retryAfter);
547             } else {
548                 triggerCompletedCallback();
549             }
550 
551             // Notify UceRequestManager to remove this instance from the collection.
552             mRequestManagerCallback.notifyRequestCoordinatorFinished(mCoordinatorId);
553 
554             logd("checkAndFinishRequestCoordinator(SubscribeRequest) done, id=" + mCoordinatorId);
555         }
556     }
557 
558     /**
559      * Trigger the capabilities updated callback.
560      */
triggerCapabilitiesReceivedCallback(List<RcsContactUceCapability> capList)561     private void triggerCapabilitiesReceivedCallback(List<RcsContactUceCapability> capList) {
562         try {
563             logd("triggerCapabilitiesCallback: size=" + capList.size());
564             mCapabilitiesCallback.onCapabilitiesReceived(capList);
565         } catch (RemoteException e) {
566             logw("triggerCapabilitiesCallback exception: " + e);
567         } finally {
568             logd("triggerCapabilitiesCallback: done");
569         }
570     }
571 
572     /**
573      * Trigger the onComplete callback to notify the request is completed.
574      */
triggerCompletedCallback()575     private void triggerCompletedCallback() {
576         try {
577             logd("triggerCompletedCallback");
578             mCapabilitiesCallback.onComplete();
579         } catch (RemoteException e) {
580             logw("triggerCompletedCallback exception: " + e);
581         } finally {
582             logd("triggerCompletedCallback: done");
583         }
584     }
585 
586     /**
587      * Trigger the onError callback to notify the request is failed.
588      */
triggerErrorCallback(int errorCode, long retryAfterMillis)589     private void triggerErrorCallback(int errorCode, long retryAfterMillis) {
590         try {
591             logd("triggerErrorCallback: errorCode=" + errorCode + ", retry=" + retryAfterMillis);
592             mCapabilitiesCallback.onError(errorCode, retryAfterMillis);
593         } catch (RemoteException e) {
594             logw("triggerErrorCallback exception: " + e);
595         } finally {
596             logd("triggerErrorCallback: done");
597         }
598     }
599 
600     @VisibleForTesting
getActivatedRequest()601     public Collection<UceRequest> getActivatedRequest() {
602         return mActivatedRequests.values();
603     }
604 
605     @VisibleForTesting
getFinishedRequest()606     public Collection<RequestResult> getFinishedRequest() {
607         return mFinishedRequests.values();
608     }
609 }
610