From efd5245026fb5436f08e70194e4265a8fbd87a55 Mon Sep 17 00:00:00 2001 From: "James.cf Lin" Date: Mon, 24 May 2021 23:13:15 +0800 Subject: [UCE] Do not block indefinitely if the framework never receive a final result for capabilities request Bug: 188440601 Test: atest -c CtsTelephonyTestCases:android.telephony.ims.cts.RcsUceAdapterTest Change-Id: I90748e29e48ebdea86015ec7384ad7be1f0b2225 --- .../com/android/ims/rcs/uce/UceController.java | 10 +++ .../ims/rcs/uce/request/CapabilityRequest.java | 18 ++++- .../ims/rcs/uce/request/OptionsRequest.java | 5 ++ .../rcs/uce/request/OptionsRequestCoordinator.java | 27 +++++++ .../rcs/uce/request/RemoteOptionsCoordinator.java | 6 ++ .../ims/rcs/uce/request/SubscribeRequest.java | 9 +++ .../uce/request/SubscribeRequestCoordinator.java | 28 ++++++- .../ims/rcs/uce/request/UceRequestCoordinator.java | 9 ++- .../ims/rcs/uce/request/UceRequestManager.java | 89 +++++++++++++++++++++- .../com/android/ims/rcs/uce/util/UceUtils.java | 27 +++++++ 10 files changed, 220 insertions(+), 8 deletions(-) diff --git a/src/java/com/android/ims/rcs/uce/UceController.java b/src/java/com/android/ims/rcs/uce/UceController.java index ea57a0ea..276f1fa2 100644 --- a/src/java/com/android/ims/rcs/uce/UceController.java +++ b/src/java/com/android/ims/rcs/uce/UceController.java @@ -731,6 +731,16 @@ public class UceController { mDeviceState.resetDeviceState(); } + /** + * Set the milliseconds of capabilities request timeout. + *

+ * Used for testing ONLY. + */ + public void setCapabilitiesRequestTimeout(long timeoutAfterMs) { + logd("setCapabilitiesRequestTimeout: " + timeoutAfterMs); + UceUtils.setCapRequestTimeoutAfterMillis(timeoutAfterMs); + } + /** * Get the subscription ID. */ diff --git a/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java b/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java index fb55b565..f7a4acc6 100644 --- a/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java +++ b/src/java/com/android/ims/rcs/uce/request/CapabilityRequest.java @@ -89,6 +89,8 @@ public abstract class CapabilityRequest implements UceRequest { @Override public void onFinish() { mIsFinished = true; + // Remove the timeout timer of this request + mRequestManagerCallback.removeRequestTimeoutTimer(mTaskId); } @Override @@ -215,16 +217,24 @@ public abstract class CapabilityRequest implements UceRequest { /** * Get the contact uris which cannot retrieve capabilities from the cache. - * @param cachedCapabilityList The capabilities which are already stored in the cache. + * @param cachedCapList The capabilities which are already stored in the cache. */ - private List getRequestingFromNetworkUris( - List cachedCapabilityList) { + private List getRequestingFromNetworkUris(List cachedCapList) { return mUriList.stream() - .filter(uri -> cachedCapabilityList.stream() + .filter(uri -> cachedCapList.stream() .noneMatch(cap -> cap.getContactUri().equals(uri))) .collect(Collectors.toList()); } + /** + * Set the timeout timer of this request. + */ + protected void setupRequestTimeoutTimer() { + long timeoutAfterMs = UceUtils.getCapRequestTimeoutAfterMillis(); + logd("setupRequestTimeoutTimer(ms): " + timeoutAfterMs); + mRequestManagerCallback.setRequestTimeoutTimer(mCoordinatorId, mTaskId, timeoutAfterMs); + } + /* * Requests capabilities from IMS. The inherited request is required to override this method * to define the behavior of requesting capabilities. diff --git a/src/java/com/android/ims/rcs/uce/request/OptionsRequest.java b/src/java/com/android/ims/rcs/uce/request/OptionsRequest.java index f0214029..df5cebbb 100644 --- a/src/java/com/android/ims/rcs/uce/request/OptionsRequest.java +++ b/src/java/com/android/ims/rcs/uce/request/OptionsRequest.java @@ -105,7 +105,10 @@ public class OptionsRequest extends CapabilityRequest { logi("requestCapabilities: featureTag size=" + featureTags.size()); try { + // Send the capabilities request. optionsController.sendCapabilitiesRequest(mContactUri, featureTags, mResponseCallback); + // Setup the timeout timer. + setupRequestTimeoutTimer(); } catch (RemoteException e) { logw("requestCapabilities exception: " + e); mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE); @@ -117,6 +120,7 @@ public class OptionsRequest extends CapabilityRequest { private void onCommandError(@CommandCode int cmdError) { logd("onCommandError: error code=" + cmdError); if (mIsFinished) { + logw("onCommandError: The request is already finished"); return; } mRequestResponse.setCommandError(cmdError); @@ -128,6 +132,7 @@ public class OptionsRequest extends CapabilityRequest { logd("onNetworkResponse: sipCode=" + sipCode + ", reason=" + reason + ", remoteCap size=" + ((remoteCaps == null) ? "null" : remoteCaps.size())); if (mIsFinished) { + logw("onNetworkResponse: The request is already finished"); return; } diff --git a/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java index 9ab5fa83..a150dd6d 100644 --- a/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java +++ b/src/java/com/android/ims/rcs/uce/request/OptionsRequestCoordinator.java @@ -20,6 +20,7 @@ import static android.telephony.ims.stub.RcsCapabilityExchangeImplBase.COMMAND_C import android.os.RemoteException; import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.RcsUceAdapter; import android.telephony.ims.aidl.IRcsUceControllerCallback; import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback; @@ -96,6 +97,11 @@ public class OptionsRequestCoordinator extends UceRequestCoordinator { private static final RequestResultCreator sNotNeedRequestFromNetworkCreator = (taskId, response) -> RequestResult.createSuccessResult(taskId); + // The RequestResult creator of the request timeout. + private static final RequestResultCreator sRequestTimeoutCreator = + (taskId, response) -> RequestResult.createFailedResult(taskId, + RcsUceAdapter.ERROR_REQUEST_TIMEOUT, 0L); + // The callback to notify the result of the capabilities request. private IRcsUceControllerCallback mCapabilitiesCallback; @@ -144,6 +150,9 @@ public class OptionsRequestCoordinator extends UceRequestCoordinator { case REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK: handleNoNeedRequestFromNetwork(request); break; + case REQUEST_UPDATE_TIMEOUT: + handleRequestTimeout(request); + break; default: logw("onRequestUpdated(OptionsRequest): invalid event " + event); break; @@ -247,6 +256,24 @@ public class OptionsRequestCoordinator extends UceRequestCoordinator { moveRequestToFinishedCollection(taskId, requestResult); } + /** + * This method is called when the framework does not receive receive the result for + * capabilities request. + */ + private void handleRequestTimeout(OptionsRequest request) { + CapabilityRequestResponse response = request.getRequestResponse(); + logd("handleRequestTimeout: " + response.toString()); + + // Finish this request. + request.onFinish(); + + // Remove this request from the activated collection and notify RequestManager. + long taskId = request.getTaskId(); + RequestResult requestResult = sRequestTimeoutCreator.createRequestResult(taskId, + response); + moveRequestToFinishedCollection(taskId, requestResult); + } + /** * Trigger the capabilities updated callback. */ diff --git a/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java b/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java index 31b68385..c8aa3f77 100644 --- a/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java +++ b/src/java/com/android/ims/rcs/uce/request/RemoteOptionsCoordinator.java @@ -143,17 +143,23 @@ public class RemoteOptionsCoordinator extends UceRequestCoordinator { private void triggerOptionsReqCallback(RcsContactUceCapability deviceCaps, boolean isRemoteNumberBlocked) { try { + logd("triggerOptionsReqCallback: start"); mOptionsReqCallback.respondToCapabilityRequest(deviceCaps, isRemoteNumberBlocked); } catch (RemoteException e) { logw("triggerOptionsReqCallback exception: " + e); + } finally { + logd("triggerOptionsReqCallback: done"); } } private void triggerOptionsReqWithErrorCallback(int errorCode, String reason) { try { + logd("triggerOptionsReqWithErrorCallback: start"); mOptionsReqCallback.respondToCapabilityRequestWithError(errorCode, reason); } catch (RemoteException e) { logw("triggerOptionsReqWithErrorCallback exception: " + e); + } finally { + logd("triggerOptionsReqWithErrorCallback: done"); } } diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java index 26493638..eb368392 100644 --- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java +++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequest.java @@ -108,7 +108,10 @@ public class SubscribeRequest extends CapabilityRequest { logi("requestCapabilities: size=" + requestCapUris.size()); try { + // Send the capabilities request. subscribeController.requestCapabilities(requestCapUris, mResponseCallback); + // Setup the timeout timer. + setupRequestTimeoutTimer(); } catch (RemoteException e) { logw("requestCapabilities exception: " + e); mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE); @@ -120,6 +123,7 @@ public class SubscribeRequest extends CapabilityRequest { private void onCommandError(@CommandCode int cmdError) { logd("onCommandError: error code=" + cmdError); if (mIsFinished) { + logw("onCommandError: request is already finished"); return; } mRequestResponse.setCommandError(cmdError); @@ -130,6 +134,7 @@ public class SubscribeRequest extends CapabilityRequest { private void onNetworkResponse(int sipCode, String reason) { logd("onNetworkResponse: code=" + sipCode + ", reason=" + reason); if (mIsFinished) { + logw("onNetworkResponse: request is already finished"); return; } mRequestResponse.setNetworkResponseCode(sipCode, reason); @@ -143,6 +148,7 @@ public class SubscribeRequest extends CapabilityRequest { ", reasonHeaderCause=" + reasonHeaderCause + ", reasonHeaderText=" + reasonHeaderText); if (mIsFinished) { + logw("onNetworkResponse: request is already finished"); return; } mRequestResponse.setNetworkResponseCode(sipCode, reasonPhrase, reasonHeaderCause, @@ -158,6 +164,7 @@ public class SubscribeRequest extends CapabilityRequest { } if (terminatedResource == null) { + logw("onResourceTerminated: the parameter is null"); terminatedResource = Collections.emptyList(); } @@ -177,6 +184,7 @@ public class SubscribeRequest extends CapabilityRequest { } if (pidfXml == null) { + logw("onCapabilitiesUpdate: The parameter is null"); pidfXml = Collections.EMPTY_LIST; } @@ -199,6 +207,7 @@ public class SubscribeRequest extends CapabilityRequest { private void onTerminated(String reason, long retryAfterMillis) { logd("onTerminated: reason=" + reason + ", retryAfter=" + retryAfterMillis); if (mIsFinished) { + logd("onTerminated: This request is already finished"); return; } mRequestResponse.setTerminated(reason, retryAfterMillis); diff --git a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java index 1b01d78b..54aa5cc9 100644 --- a/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java +++ b/src/java/com/android/ims/rcs/uce/request/SubscribeRequestCoordinator.java @@ -139,6 +139,11 @@ public class SubscribeRequestCoordinator extends UceRequestCoordinator { private static final RequestResultCreator sNotNeedRequestFromNetworkCreator = (taskId, response, requestMgrCallback) -> RequestResult.createSuccessResult(taskId); + // The RequestResult creator of the request timeout. + private static final RequestResultCreator sRequestTimeoutCreator = + (taskId, response, requestMgrCallback) -> RequestResult.createFailedResult(taskId, + RcsUceAdapter.ERROR_REQUEST_TIMEOUT, 0L); + // The callback to notify the result of the capabilities request. private volatile IRcsUceControllerCallback mCapabilitiesCallback; @@ -196,8 +201,11 @@ public class SubscribeRequestCoordinator extends UceRequestCoordinator { case REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK: handleNoNeedRequestFromNetwork(request); break; + case REQUEST_UPDATE_TIMEOUT: + handleRequestTimeout(request); + break; default: - logw("onRequestUpdated: invalid event " + event); + logw("onRequestUpdated(SubscribeRequest): invalid event " + event); break; } @@ -393,6 +401,24 @@ public class SubscribeRequestCoordinator extends UceRequestCoordinator { moveRequestToFinishedCollection(taskId, requestResult); } + /** + * This method is called when the framework does not receive receive the result for + * capabilities request. + */ + private void handleRequestTimeout(SubscribeRequest request) { + CapabilityRequestResponse response = request.getRequestResponse(); + logd("handleRequestTimeout: " + response); + + // Finish this request + request.onFinish(); + + // Remove this request from the activated collection and notify RequestManager. + long taskId = request.getTaskId(); + RequestResult requestResult = sRequestTimeoutCreator.createRequestResult(taskId, + response, mRequestManagerCallback); + moveRequestToFinishedCollection(taskId, requestResult); + } + private void checkAndFinishRequestCoordinator() { synchronized (mCollectionLock) { // Return because there are requests running. diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java b/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java index 2cb37773..eea4fbe3 100644 --- a/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java +++ b/src/java/com/android/ims/rcs/uce/request/UceRequestCoordinator.java @@ -87,6 +87,11 @@ public abstract class UceRequestCoordinator { */ public static final int REQUEST_UPDATE_REMOTE_REQUEST_DONE = 8; + /** + * The capabilities request is timeout. + */ + public static final int REQUEST_UPDATE_TIMEOUT = 9; + @IntDef(value = { REQUEST_UPDATE_ERROR, REQUEST_UPDATE_COMMAND_ERROR, @@ -97,6 +102,7 @@ public abstract class UceRequestCoordinator { REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE, REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK, REQUEST_UPDATE_REMOTE_REQUEST_DONE, + REQUEST_UPDATE_TIMEOUT, }, prefix="REQUEST_UPDATE_") @Retention(RetentionPolicy.SOURCE) @interface UceRequestUpdate {} @@ -111,7 +117,8 @@ public abstract class UceRequestCoordinator { REQUEST_EVENT_DESC.put(REQUEST_UPDATE_CAPABILITY_UPDATE, "REQUEST_CAPABILITY_UPDATE"); REQUEST_EVENT_DESC.put(REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE, "REQUEST_CACHE_CAP_UPDATE"); REQUEST_EVENT_DESC.put(REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK, "NO_NEED_REQUEST"); - REQUEST_EVENT_DESC.put(REQUEST_UPDATE_REMOTE_REQUEST_DONE, "REMOTE_REQ_DONE"); + REQUEST_EVENT_DESC.put(REQUEST_UPDATE_REMOTE_REQUEST_DONE, "REMOTE_REQUEST_DONE"); + REQUEST_EVENT_DESC.put(REQUEST_UPDATE_TIMEOUT, "REQUEST_TIMEOUT"); } /** diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java index 2495aa47..5e428e98 100644 --- a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java +++ b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java @@ -219,6 +219,16 @@ public class UceRequestManager { */ void notifyRemoteRequestDone(long requestCoordinatorId, long taskId); + /** + * Set the timer for the request timeout. It will cancel the request when the time is up. + */ + void setRequestTimeoutTimer(long requestCoordinatorId, long taskId, long timeoutAfterMs); + + /** + * Remove the timeout timer of the capabilities request. + */ + void removeRequestTimeoutTimer(long taskId); + /** * Notify that the UceRequest has finished. This is sent by UceRequestCoordinator. */ @@ -318,6 +328,16 @@ public class UceRequestManager { UceRequestCoordinator.REQUEST_UPDATE_REMOTE_REQUEST_DONE); } + @Override + public void setRequestTimeoutTimer(long coordinatorId, long taskId, long timeoutAfterMs) { + mHandler.sendRequestTimeoutTimerMessage(coordinatorId, taskId, timeoutAfterMs); + } + + @Override + public void removeRequestTimeoutTimer(long taskId) { + mHandler.removeRequestTimeoutTimer(taskId); + } + @Override public void notifyUceRequestFinished(long requestCoordinatorId, long taskId) { mHandler.sendRequestFinishedMessage(requestCoordinatorId, taskId); @@ -547,13 +567,16 @@ public class UceRequestManager { private static class UceRequestHandler extends Handler { private static final int EVENT_EXECUTE_REQUEST = 1; private static final int EVENT_REQUEST_UPDATED = 2; - private static final int EVENT_REQUEST_FINISHED = 3; - private static final int EVENT_COORDINATOR_FINISHED = 4; + private static final int EVENT_REQUEST_TIMEOUT = 3; + private static final int EVENT_REQUEST_FINISHED = 4; + private static final int EVENT_COORDINATOR_FINISHED = 5; + private final Map mRequestTimeoutTimers; private final WeakReference mUceRequestMgrRef; public UceRequestHandler(UceRequestManager requestManager, Looper looper) { super(looper); + mRequestTimeoutTimers = new HashMap<>(); mUceRequestMgrRef = new WeakReference<>(requestManager); } @@ -587,6 +610,41 @@ public class UceRequestManager { sendMessage(message); } + /** + * Set the timeout timer to cancel the capabilities request. + */ + public void sendRequestTimeoutTimerMessage(Long coordId, Long taskId, Long timeoutAfterMs) { + synchronized (mRequestTimeoutTimers) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = coordId; + args.arg2 = taskId; + + // Add the message object to the collection. It can be used to find this message + // when the request is completed and remove the timeout timer. + mRequestTimeoutTimers.put(taskId, args); + + Message message = obtainMessage(); + message.what = EVENT_REQUEST_TIMEOUT; + message.obj = args; + sendMessageDelayed(message, timeoutAfterMs); + } + } + + /** + * Remove the timeout timer because the capabilities request is finished. + */ + public void removeRequestTimeoutTimer(Long taskId) { + synchronized (mRequestTimeoutTimers) { + SomeArgs args = mRequestTimeoutTimers.remove(taskId); + if (args == null) { + return; + } + Log.d(LOG_TAG, "removeRequestTimeoutTimer: taskId=" + taskId); + removeMessages(EVENT_REQUEST_TIMEOUT, args); + args.recycle(); + } + } + public void sendRequestFinishedMessage(Long coordinatorId, Long taskId) { SomeArgs args = SomeArgs.obtain(); args.arg1 = coordinatorId; @@ -616,6 +674,15 @@ public class UceRequestManager { */ public void onDestroy() { removeCallbacksAndMessages(null); + // Recycle all the arguments in the mRequestTimeoutTimers + synchronized (mRequestTimeoutTimers) { + mRequestTimeoutTimers.forEach((taskId, args) -> { + try { + args.recycle(); + } catch (Exception e) {} + }); + mRequestTimeoutTimers.clear(); + } } @Override @@ -652,7 +719,24 @@ public class UceRequestManager { requestCoordinator.onRequestUpdated(taskId, requestEvent); break; } + case EVENT_REQUEST_TIMEOUT: { + UceRequestCoordinator requestCoordinator = + requestManager.getRequestCoordinator(coordinatorId); + if (requestCoordinator == null) { + requestManager.logw("handleMessage: cannot find UceRequestCoordinator"); + return; + } + // The timeout timer is triggered, remove this record from the collection. + synchronized (mRequestTimeoutTimers) { + mRequestTimeoutTimers.remove(taskId); + } + // Notify that the request is timeout. + requestCoordinator.onRequestUpdated(taskId, + UceRequestCoordinator.REQUEST_UPDATE_TIMEOUT); + break; + } case EVENT_REQUEST_FINISHED: { + // Notify the repository that the request is finished. requestManager.notifyRepositoryRequestFinished(taskId); break; } @@ -674,6 +758,7 @@ public class UceRequestManager { static { EVENT_DESCRIPTION.put(EVENT_EXECUTE_REQUEST, "EXECUTE_REQUEST"); EVENT_DESCRIPTION.put(EVENT_REQUEST_UPDATED, "REQUEST_UPDATE"); + EVENT_DESCRIPTION.put(EVENT_REQUEST_TIMEOUT, "REQUEST_TIMEOUT"); EVENT_DESCRIPTION.put(EVENT_REQUEST_FINISHED, "REQUEST_FINISHED"); EVENT_DESCRIPTION.put(EVENT_COORDINATOR_FINISHED, "REMOVE_COORDINATOR"); } diff --git a/src/java/com/android/ims/rcs/uce/util/UceUtils.java b/src/java/com/android/ims/rcs/uce/util/UceUtils.java index 93bdbd52..6db0d23e 100644 --- a/src/java/com/android/ims/rcs/uce/util/UceUtils.java +++ b/src/java/com/android/ims/rcs/uce/util/UceUtils.java @@ -48,6 +48,9 @@ public class UceUtils { private static final long DEFAULT_REQUEST_RETRY_INTERVAL_MS = TimeUnit.MINUTES.toMillis(20); private static final long DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS = TimeUnit.SECONDS.toMillis(3); + private static final long DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS = TimeUnit.MINUTES.toMillis(1); + private static Optional OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); + // The task ID of the UCE request private static long TASK_ID = 0L; @@ -349,4 +352,28 @@ public class UceUtils { public static long getMinimumRequestRetryAfterMillis() { return DEFAULT_MINIMUM_REQUEST_RETRY_AFTER_MS; } + + /** + * Override the capability request timeout to the millisecond value specified. Sending a + * value <= 0 will reset the capabilities. + */ + public static synchronized void setCapRequestTimeoutAfterMillis(long timeoutAfterMs) { + if (timeoutAfterMs <= 0L) { + OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.empty(); + } else { + OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS = Optional.of(timeoutAfterMs); + } + } + + /** + * Get the milliseconds of the capabilities request timed out. + * @return the time in milliseconds before a pending capabilities request will time out. + */ + public static synchronized long getCapRequestTimeoutAfterMillis() { + if(OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.isPresent()) { + return OVERRIDE_CAP_REQUEST_TIMEOUT_AFTER_MS.get(); + } else { + return DEFAULT_CAP_REQUEST_TIMEOUT_AFTER_MS; + } + } } -- cgit v1.2.3