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.os.RemoteException;
22 import android.telephony.ims.RcsContactUceCapability;
23 import android.telephony.ims.RcsUceAdapter;
24 import android.telephony.ims.aidl.IRcsUceControllerCallback;
25 
26 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
27 import com.android.ims.rcs.uce.UceStatsWriter;
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.util.Collection;
31 import java.util.Comparator;
32 import java.util.List;
33 import java.util.Optional;
34 
35 /**
36  * Responsible for the communication and interaction between OptionsRequests and triggering
37  * the callback to notify the result of the capabilities request.
38  */
39 public class OptionsRequestCoordinator extends UceRequestCoordinator {
40     /**
41      * The builder of the OptionsRequestCoordinator.
42      */
43     public static final class Builder {
44         private OptionsRequestCoordinator mRequestCoordinator;
45 
Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback callback)46         public Builder(int subId, Collection<UceRequest> requests,
47                 RequestManagerCallback callback) {
48             mRequestCoordinator = new OptionsRequestCoordinator(subId, requests, callback,
49                     UceStatsWriter.getInstance());
50         }
51         @VisibleForTesting
Builder(int subId, Collection<UceRequest> requests, RequestManagerCallback callback, UceStatsWriter instance)52         public Builder(int subId, Collection<UceRequest> requests,
53                 RequestManagerCallback callback, UceStatsWriter instance) {
54             mRequestCoordinator = new OptionsRequestCoordinator(subId, requests, callback,
55                     instance);
56         }
57 
setCapabilitiesCallback(IRcsUceControllerCallback callback)58         public Builder setCapabilitiesCallback(IRcsUceControllerCallback callback) {
59             mRequestCoordinator.setCapabilitiesCallback(callback);
60             return this;
61         }
62 
build()63         public OptionsRequestCoordinator build() {
64             return mRequestCoordinator;
65         }
66     }
67 
68     /**
69      * Different request updated events will create different {@link RequestResult}. Define the
70      * interface to get the {@link RequestResult} instance according to the given task ID and
71      * {@link CapabilityRequestResponse}.
72      */
73     @FunctionalInterface
74     private interface RequestResultCreator {
createRequestResult(long taskId, CapabilityRequestResponse response)75         RequestResult createRequestResult(long taskId, CapabilityRequestResponse response);
76     }
77 
78     // The RequestResult creator of the request error.
79     private static final RequestResultCreator sRequestErrorCreator = (taskId, response) -> {
80         int errorCode = response.getRequestInternalError().orElse(DEFAULT_ERROR_CODE);
81         long retryAfter = response.getRetryAfterMillis();
82         return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
83     };
84 
85     // The RequestResult creator of the request command error.
86     private static final RequestResultCreator sCommandErrorCreator = (taskId, response) -> {
87         int cmdError = response.getCommandError().orElse(COMMAND_CODE_GENERIC_FAILURE);
88         int errorCode = CapabilityRequestResponse.getCapabilityErrorFromCommandError(cmdError);
89         long retryAfter = response.getRetryAfterMillis();
90         return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
91     };
92 
93     // The RequestResult creator of the network response.
94     private static final RequestResultCreator sNetworkRespCreator = (taskId, response) -> {
95         if (response.isNetworkResponseOK()) {
96             return RequestResult.createSuccessResult(taskId);
97         } else {
98             int errorCode = CapabilityRequestResponse.getCapabilityErrorFromSipCode(response);
99             long retryAfter = response.getRetryAfterMillis();
100             return RequestResult.createFailedResult(taskId, errorCode, retryAfter);
101         }
102     };
103 
104     // The RequestResult creator for does not need to request from the network.
105     private static final RequestResultCreator sNotNeedRequestFromNetworkCreator =
106             (taskId, response) -> RequestResult.createSuccessResult(taskId);
107 
108     // The RequestResult creator of the request timeout.
109     private static final RequestResultCreator sRequestTimeoutCreator =
110             (taskId, response) -> RequestResult.createFailedResult(taskId,
111                     RcsUceAdapter.ERROR_REQUEST_TIMEOUT, 0L);
112 
113     // The callback to notify the result of the capabilities request.
114     private IRcsUceControllerCallback mCapabilitiesCallback;
115 
116     private final UceStatsWriter mUceStatsWriter;
117 
OptionsRequestCoordinator(int subId, Collection<UceRequest> requests, RequestManagerCallback requestMgrCallback, UceStatsWriter instance)118     private OptionsRequestCoordinator(int subId, Collection<UceRequest> requests,
119             RequestManagerCallback requestMgrCallback, UceStatsWriter instance) {
120         super(subId, requests, requestMgrCallback);
121         mUceStatsWriter = instance;
122         logd("OptionsRequestCoordinator: created");
123     }
124 
setCapabilitiesCallback(IRcsUceControllerCallback callback)125     private void setCapabilitiesCallback(IRcsUceControllerCallback callback) {
126         mCapabilitiesCallback = callback;
127     }
128 
129     @Override
onFinish()130     public void onFinish() {
131         logd("OptionsRequestCoordinator: onFinish");
132         mCapabilitiesCallback = null;
133         super.onFinish();
134     }
135 
136     @Override
onRequestUpdated(long taskId, @UceRequestUpdate int event)137     public void onRequestUpdated(long taskId, @UceRequestUpdate int event) {
138         if (mIsFinished) return;
139         OptionsRequest request = (OptionsRequest) getUceRequest(taskId);
140         if (request == null) {
141             logw("onRequestUpdated: Cannot find OptionsRequest taskId=" + taskId);
142             return;
143         }
144 
145         logd("onRequestUpdated(OptionsRequest): taskId=" + taskId + ", event=" +
146                 REQUEST_EVENT_DESC.get(event));
147 
148         switch (event) {
149             case REQUEST_UPDATE_ERROR:
150                 handleRequestError(request);
151                 break;
152             case REQUEST_UPDATE_COMMAND_ERROR:
153                 handleCommandError(request);
154                 break;
155             case REQUEST_UPDATE_NETWORK_RESPONSE:
156                 handleNetworkResponse(request);
157                 break;
158             case REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE:
159                 handleCachedCapabilityUpdated(request);
160                 break;
161             case REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK:
162                 handleNoNeedRequestFromNetwork(request);
163                 break;
164             case REQUEST_UPDATE_TIMEOUT:
165                 handleRequestTimeout(request);
166                 break;
167             default:
168                 logw("onRequestUpdated(OptionsRequest): invalid event " + event);
169                 break;
170         }
171 
172         // End this instance if all the UceRequests in the coordinator are finished.
173         checkAndFinishRequestCoordinator();
174     }
175 
176     /**
177      * Finish the OptionsRequest because it has encountered error.
178      */
handleRequestError(OptionsRequest request)179     private void handleRequestError(OptionsRequest request) {
180         CapabilityRequestResponse response = request.getRequestResponse();
181         logd("handleRequestError: " + request.toString());
182 
183         // Finish this request.
184         request.onFinish();
185 
186         // Remove this request from the activated collection and notify RequestManager.
187         Long taskId = request.getTaskId();
188         RequestResult requestResult = sRequestErrorCreator.createRequestResult(taskId, response);
189         moveRequestToFinishedCollection(taskId, requestResult);
190     }
191 
192     /**
193      * This method is called when the given OptionsRequest received the onCommandError callback
194      * from the ImsService.
195      */
handleCommandError(OptionsRequest request)196     private void handleCommandError(OptionsRequest request) {
197         CapabilityRequestResponse response = request.getRequestResponse();
198         logd("handleCommandError: " + request.toString());
199 
200         // Finish this request.
201         request.onFinish();
202 
203         int commandErrorCode = response.getCommandError().orElse(0);
204         mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.OUTGOING_OPTION_EVENT,
205             false, commandErrorCode, 0);
206 
207 
208         // Remove this request from the activated collection and notify RequestManager.
209         Long taskId = request.getTaskId();
210         RequestResult requestResult = sCommandErrorCreator.createRequestResult(taskId, response);
211         moveRequestToFinishedCollection(taskId, requestResult);
212     }
213 
214     /**
215      * This method is called when the given OptionsRequest received the onNetworkResponse
216      * callback from the ImsService.
217      */
handleNetworkResponse(OptionsRequest request)218     private void handleNetworkResponse(OptionsRequest request) {
219         CapabilityRequestResponse response = request.getRequestResponse();
220         logd("handleNetworkResponse: " + response.toString());
221 
222         int responseCode = response.getNetworkRespSipCode().orElse(0);
223         mUceStatsWriter.setUceEvent(mSubId, UceStatsWriter.OUTGOING_OPTION_EVENT, true,
224             0, responseCode);
225 
226 
227         List<RcsContactUceCapability> updatedCapList = response.getUpdatedContactCapability();
228         if (!updatedCapList.isEmpty()) {
229             // Save the capabilities and trigger the capabilities callback
230             mRequestManagerCallback.saveCapabilities(updatedCapList);
231             triggerCapabilitiesReceivedCallback(updatedCapList);
232             response.removeUpdatedCapabilities(updatedCapList);
233         }
234 
235         // Finish this request.
236         request.onFinish();
237 
238         // Remove this request from the activated collection and notify RequestManager.
239         Long taskId = request.getTaskId();
240         RequestResult requestResult = sNetworkRespCreator.createRequestResult(taskId, response);
241         moveRequestToFinishedCollection(taskId, requestResult);
242     }
243 
244     /**
245      * This method is called when the OptionsRequest retrieves the capabilities from cache.
246      */
handleCachedCapabilityUpdated(OptionsRequest request)247     private void handleCachedCapabilityUpdated(OptionsRequest request) {
248         CapabilityRequestResponse response = request.getRequestResponse();
249         Long taskId = request.getTaskId();
250         List<RcsContactUceCapability> cachedCapList = response.getCachedContactCapability();
251         logd("handleCachedCapabilityUpdated: taskId=" + taskId + ", CapRequestResp=" + response);
252 
253         if (cachedCapList.isEmpty()) {
254             return;
255         }
256 
257         // Trigger the capabilities updated callback.
258         triggerCapabilitiesReceivedCallback(cachedCapList);
259         response.removeCachedContactCapabilities();
260     }
261 
262     /**
263      * This method is called when all the capabilities can be retrieved from the cached and it does
264      * not need to request capabilities from the network.
265      */
handleNoNeedRequestFromNetwork(OptionsRequest request)266     private void handleNoNeedRequestFromNetwork(OptionsRequest request) {
267         CapabilityRequestResponse response = request.getRequestResponse();
268         logd("handleNoNeedRequestFromNetwork: " + response.toString());
269 
270         // Finish this request.
271         request.onFinish();
272 
273         // Remove this request from the activated collection and notify RequestManager.
274         long taskId = request.getTaskId();
275         RequestResult requestResult = sNotNeedRequestFromNetworkCreator.createRequestResult(taskId,
276                 response);
277         moveRequestToFinishedCollection(taskId, requestResult);
278     }
279 
280     /**
281      * This method is called when the framework does not receive receive the result for
282      * capabilities request.
283      */
handleRequestTimeout(OptionsRequest request)284     private void handleRequestTimeout(OptionsRequest request) {
285         CapabilityRequestResponse response = request.getRequestResponse();
286         logd("handleRequestTimeout: " + response.toString());
287 
288         // Finish this request.
289         request.onFinish();
290 
291         // Remove this request from the activated collection and notify RequestManager.
292         long taskId = request.getTaskId();
293         RequestResult requestResult = sRequestTimeoutCreator.createRequestResult(taskId,
294                 response);
295         moveRequestToFinishedCollection(taskId, requestResult);
296     }
297 
298     /**
299      * Trigger the capabilities updated callback.
300      */
triggerCapabilitiesReceivedCallback(List<RcsContactUceCapability> capList)301     private void triggerCapabilitiesReceivedCallback(List<RcsContactUceCapability> capList) {
302         try {
303             logd("triggerCapabilitiesCallback: size=" + capList.size());
304             mCapabilitiesCallback.onCapabilitiesReceived(capList);
305         } catch (RemoteException e) {
306             logw("triggerCapabilitiesCallback exception: " + e);
307         } finally {
308             logd("triggerCapabilitiesCallback: done");
309         }
310     }
311 
312     /**
313      * Trigger the onComplete callback to notify the request is completed.
314      */
triggerCompletedCallback()315     private void triggerCompletedCallback() {
316         try {
317             logd("triggerCompletedCallback");
318             mCapabilitiesCallback.onComplete(null);
319         } catch (RemoteException e) {
320             logw("triggerCompletedCallback exception: " + e);
321         } finally {
322             logd("triggerCompletedCallback: done");
323         }
324     }
325 
326     /**
327      * Trigger the onError callback to notify the request is failed.
328      */
triggerErrorCallback(int errorCode, long retryAfterMillis)329     private void triggerErrorCallback(int errorCode, long retryAfterMillis) {
330         try {
331             logd("triggerErrorCallback: errorCode=" + errorCode + ", retry=" + retryAfterMillis);
332             mCapabilitiesCallback.onError(errorCode, retryAfterMillis, null);
333         } catch (RemoteException e) {
334             logw("triggerErrorCallback exception: " + e);
335         } finally {
336             logd("triggerErrorCallback: done");
337         }
338     }
339 
checkAndFinishRequestCoordinator()340     private void checkAndFinishRequestCoordinator() {
341         synchronized (mCollectionLock) {
342             // Return because there are requests running.
343             if (!mActivatedRequests.isEmpty()) {
344                 return;
345             }
346 
347             // All the requests has finished, find the request which has the max retryAfter time.
348             // If the result is empty, it means all the request are success.
349             Optional<RequestResult> optRequestResult =
350                     mFinishedRequests.values().stream()
351                             .filter(result -> !result.isRequestSuccess())
352                             .max(Comparator.comparingLong(result ->
353                                     result.getRetryMillis().orElse(-1L)));
354 
355             // Trigger the callback
356             if (optRequestResult.isPresent()) {
357                 RequestResult result = optRequestResult.get();
358                 int errorCode = result.getErrorCode().orElse(DEFAULT_ERROR_CODE);
359                 long retryAfter = result.getRetryMillis().orElse(0L);
360                 triggerErrorCallback(errorCode, retryAfter);
361             } else {
362                 triggerCompletedCallback();
363             }
364 
365             // Notify UceRequestManager to remove this instance from the collection.
366             mRequestManagerCallback.notifyRequestCoordinatorFinished(mCoordinatorId);
367 
368             logd("checkAndFinishRequestCoordinator(OptionsRequest) done, id=" + mCoordinatorId);
369         }
370     }
371 
372     @VisibleForTesting
getActivatedRequest()373     public Collection<UceRequest> getActivatedRequest() {
374         return mActivatedRequests.values();
375     }
376 
377     @VisibleForTesting
getFinishedRequest()378     public Collection<RequestResult> getFinishedRequest() {
379         return mFinishedRequests.values();
380     }
381 }
382