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