diff options
Diffstat (limited to 'src/java/com/android/ims/rcs/uce/request/UceRequestManager.java')
-rw-r--r-- | src/java/com/android/ims/rcs/uce/request/UceRequestManager.java | 829 |
1 files changed, 829 insertions, 0 deletions
diff --git a/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java new file mode 100644 index 00000000..3e12ba30 --- /dev/null +++ b/src/java/com/android/ims/rcs/uce/request/UceRequestManager.java @@ -0,0 +1,829 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ims.rcs.uce.request; + +import android.content.Context; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.RcsContactUceCapability.CapabilityMechanism; +import android.telephony.ims.RcsUceAdapter; +import android.telephony.ims.aidl.IOptionsRequestCallback; +import android.telephony.ims.aidl.IRcsUceControllerCallback; +import android.text.TextUtils; +import android.util.Log; + +import com.android.ims.rcs.uce.UceController; +import com.android.ims.rcs.uce.UceController.UceControllerCallback; +import com.android.ims.rcs.uce.UceDeviceState; +import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult; +import com.android.ims.rcs.uce.eab.EabCapabilityResult; +import com.android.ims.rcs.uce.options.OptionsController; +import com.android.ims.rcs.uce.presence.subscribe.SubscribeController; +import com.android.ims.rcs.uce.request.UceRequest.UceRequestType; +import com.android.ims.rcs.uce.request.UceRequestCoordinator.UceRequestUpdate; +import com.android.ims.rcs.uce.util.UceUtils; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Managers the capabilities requests and the availability requests from UceController. + */ +public class UceRequestManager { + + private static final String LOG_TAG = UceUtils.getLogPrefix() + "UceRequestManager"; + + /** + * Testing interface used to mock UceUtils in testing. + */ + @VisibleForTesting + public interface UceUtilsProxy { + /** + * The interface for {@link UceUtils#isPresenceCapExchangeEnabled(Context, int)} used for + * testing. + */ + boolean isPresenceCapExchangeEnabled(Context context, int subId); + + /** + * The interface for {@link UceUtils#isPresenceSupported(Context, int)} used for testing. + */ + boolean isPresenceSupported(Context context, int subId); + + /** + * The interface for {@link UceUtils#isSipOptionsSupported(Context, int)} used for testing. + */ + boolean isSipOptionsSupported(Context context, int subId); + + /** + * @return true when the Presence group subscribe is enabled. + */ + boolean isPresenceGroupSubscribeEnabled(Context context, int subId); + + /** + * Retrieve the maximum number of contacts that can be included in a request. + */ + int getRclMaxNumberEntries(int subId); + + /** + * @return true if the given phone number is blocked by the network. + */ + boolean isNumberBlocked(Context context, String phoneNumber); + } + + private static UceUtilsProxy sUceUtilsProxy = new UceUtilsProxy() { + @Override + public boolean isPresenceCapExchangeEnabled(Context context, int subId) { + return UceUtils.isPresenceCapExchangeEnabled(context, subId); + } + + @Override + public boolean isPresenceSupported(Context context, int subId) { + return UceUtils.isPresenceSupported(context, subId); + } + + @Override + public boolean isSipOptionsSupported(Context context, int subId) { + return UceUtils.isSipOptionsSupported(context, subId); + } + + @Override + public boolean isPresenceGroupSubscribeEnabled(Context context, int subId) { + return UceUtils.isPresenceGroupSubscribeEnabled(context, subId); + } + + @Override + public int getRclMaxNumberEntries(int subId) { + return UceUtils.getRclMaxNumberEntries(subId); + } + + @Override + public boolean isNumberBlocked(Context context, String phoneNumber) { + return UceUtils.isNumberBlocked(context, phoneNumber); + } + }; + + @VisibleForTesting + public void setsUceUtilsProxy(UceUtilsProxy uceUtilsProxy) { + sUceUtilsProxy = uceUtilsProxy; + } + + /** + * The callback interface to receive the request and the result from the UceRequest. + */ + public interface RequestManagerCallback { + /** + * Notify sending the UceRequest + */ + void notifySendingRequest(long coordinator, long taskId, long delayTimeMs); + + /** + * Retrieve the contact capabilities from the cache. + */ + List<EabCapabilityResult> getCapabilitiesFromCache(List<Uri> uriList); + + /** + * Retrieve the contact availability from the cache. + */ + EabCapabilityResult getAvailabilityFromCache(Uri uri); + + /** + * Store the given contact capabilities to the cache. + */ + void saveCapabilities(List<RcsContactUceCapability> contactCapabilities); + + /** + * Retrieve the device's capabilities. + */ + RcsContactUceCapability getDeviceCapabilities(@CapabilityMechanism int capMechanism); + + /** + * Get the device state to check whether the device is disallowed by the network or not. + */ + DeviceStateResult getDeviceState(); + + /** + * Refresh the device state. It is called when receive the UCE request response. + */ + void refreshDeviceState(int sipCode, String reason); + + /** + * Notify that the UceRequest associated with the given taskId encounters error. + */ + void notifyRequestError(long requestCoordinatorId, long taskId); + + /** + * Notify that the UceRequest received the onCommandError callback from the ImsService. + */ + void notifyCommandError(long requestCoordinatorId, long taskId); + + /** + * Notify that the UceRequest received the onNetworkResponse callback from the ImsService. + */ + void notifyNetworkResponse(long requestCoordinatorId, long taskId); + + /** + * Notify that the UceRequest received the onTerminated callback from the ImsService. + */ + void notifyTerminated(long requestCoordinatorId, long taskId); + + /** + * Notify that some contacts are not RCS anymore. It will updated the cached capabilities + * and trigger the callback IRcsUceControllerCallback#onCapabilitiesReceived + */ + void notifyResourceTerminated(long requestCoordinatorId, long taskId); + + /** + * Notify that the capabilities updates. It will update the cached and trigger the callback + * IRcsUceControllerCallback#onCapabilitiesReceived + */ + void notifyCapabilitiesUpdated(long requestCoordinatorId, long taskId); + + /** + * Notify that some of the request capabilities can be retrieved from the cached. + */ + void notifyCachedCapabilitiesUpdated(long requestCoordinatorId, long taskId); + + /** + * Notify that all the requested capabilities can be retrieved from the cache. It does not + * need to request capabilities from the network. + */ + void notifyNoNeedRequestFromNetwork(long requestCoordinatorId, long taskId); + + /** + * Notify that the remote options request is done. This is sent by RemoteOptionsRequest and + * it will notify the RemoteOptionsCoordinator to handle it. + */ + 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. + */ + void notifyUceRequestFinished(long requestCoordinatorId, long taskId); + + /** + * Notify that the RequestCoordinator has finished. This is sent by UceRequestCoordinator + * to remove the coordinator from the UceRequestRepository. + */ + void notifyRequestCoordinatorFinished(long requestCoordinatorId); + } + + private RequestManagerCallback mRequestMgrCallback = new RequestManagerCallback() { + @Override + public void notifySendingRequest(long coordinatorId, long taskId, long delayTimeMs) { + mHandler.sendRequestMessage(coordinatorId, taskId, delayTimeMs); + } + + @Override + public List<EabCapabilityResult> getCapabilitiesFromCache(List<Uri> uriList) { + return mControllerCallback.getCapabilitiesFromCache(uriList); + } + + @Override + public EabCapabilityResult getAvailabilityFromCache(Uri uri) { + return mControllerCallback.getAvailabilityFromCache(uri); + } + + @Override + public void saveCapabilities(List<RcsContactUceCapability> contactCapabilities) { + mControllerCallback.saveCapabilities(contactCapabilities); + } + + @Override + public RcsContactUceCapability getDeviceCapabilities(@CapabilityMechanism int mechanism) { + return mControllerCallback.getDeviceCapabilities(mechanism); + } + + @Override + public DeviceStateResult getDeviceState() { + return mControllerCallback.getDeviceState(); + } + + @Override + public void refreshDeviceState(int sipCode, String reason) { + mControllerCallback.refreshDeviceState(sipCode, reason, + UceController.REQUEST_TYPE_CAPABILITY); + } + + @Override + public void notifyRequestError(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_ERROR); + } + + @Override + public void notifyCommandError(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_COMMAND_ERROR); + } + + @Override + public void notifyNetworkResponse(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_NETWORK_RESPONSE); + } + @Override + public void notifyTerminated(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_TERMINATED); + } + @Override + public void notifyResourceTerminated(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_RESOURCE_TERMINATED); + } + @Override + public void notifyCapabilitiesUpdated(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_CAPABILITY_UPDATE); + } + + @Override + public void notifyCachedCapabilitiesUpdated(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE); + } + + @Override + public void notifyNoNeedRequestFromNetwork(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + UceRequestCoordinator.REQUEST_UPDATE_NO_NEED_REQUEST_FROM_NETWORK); + } + + @Override + public void notifyRemoteRequestDone(long requestCoordinatorId, long taskId) { + mHandler.sendRequestUpdatedMessage(requestCoordinatorId, taskId, + 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); + } + + @Override + public void notifyRequestCoordinatorFinished(long requestCoordinatorId) { + mHandler.sendRequestCoordinatorFinishedMessage(requestCoordinatorId); + } + }; + + private final int mSubId; + private final Context mContext; + private final UceRequestHandler mHandler; + private final UceRequestRepository mRequestRepository; + private volatile boolean mIsDestroyed; + + private OptionsController mOptionsCtrl; + private SubscribeController mSubscribeCtrl; + private UceControllerCallback mControllerCallback; + + public UceRequestManager(Context context, int subId, Looper looper, UceControllerCallback c) { + mSubId = subId; + mContext = context; + mControllerCallback = c; + mHandler = new UceRequestHandler(this, looper); + mRequestRepository = new UceRequestRepository(subId, mRequestMgrCallback); + logi("create"); + } + + @VisibleForTesting + public UceRequestManager(Context context, int subId, Looper looper, UceControllerCallback c, + UceRequestRepository requestRepository) { + mSubId = subId; + mContext = context; + mControllerCallback = c; + mHandler = new UceRequestHandler(this, looper); + mRequestRepository = requestRepository; + } + + /** + * Set the OptionsController for requestiong capabilities by OPTIONS mechanism. + */ + public void setOptionsController(OptionsController controller) { + mOptionsCtrl = controller; + } + + /** + * Set the SubscribeController for requesting capabilities by Subscribe mechanism. + */ + public void setSubscribeController(SubscribeController controller) { + mSubscribeCtrl = controller; + } + + /** + * Notify that the request manager instance is destroyed. + */ + public void onDestroy() { + logi("onDestroy"); + mIsDestroyed = true; + mHandler.onDestroy(); + mRequestRepository.onDestroy(); + } + + /** + * Send a new capability request. It is called by UceController. + */ + public void sendCapabilityRequest(List<Uri> uriList, boolean skipFromCache, + IRcsUceControllerCallback callback) throws RemoteException { + if (mIsDestroyed) { + callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L); + return; + } + sendRequestInternal(UceRequest.REQUEST_TYPE_CAPABILITY, uriList, skipFromCache, callback); + } + + /** + * Send a new availability request. It is called by UceController. + */ + public void sendAvailabilityRequest(Uri uri, IRcsUceControllerCallback callback) + throws RemoteException { + if (mIsDestroyed) { + callback.onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L); + return; + } + sendRequestInternal(UceRequest.REQUEST_TYPE_AVAILABILITY, + Collections.singletonList(uri), false /* skipFromCache */, callback); + } + + private void sendRequestInternal(@UceRequestType int type, List<Uri> uriList, + boolean skipFromCache, IRcsUceControllerCallback callback) throws RemoteException { + UceRequestCoordinator requestCoordinator = null; + if (sUceUtilsProxy.isPresenceCapExchangeEnabled(mContext, mSubId) && + sUceUtilsProxy.isPresenceSupported(mContext, mSubId)) { + requestCoordinator = createSubscribeRequestCoordinator(type, uriList, skipFromCache, + callback); + } else if (sUceUtilsProxy.isSipOptionsSupported(mContext, mSubId)) { + requestCoordinator = createOptionsRequestCoordinator(type, uriList, callback); + } + + if (requestCoordinator == null) { + logw("sendRequestInternal: Neither Presence nor OPTIONS are supported"); + callback.onError(RcsUceAdapter.ERROR_NOT_ENABLED, 0L); + return; + } + + StringBuilder builder = new StringBuilder("sendRequestInternal: "); + builder.append("requestType=").append(type) + .append(", requestCoordinatorId=").append(requestCoordinator.getCoordinatorId()) + .append(", taskId={") + .append(requestCoordinator.getActivatedRequestTaskIds().stream() + .map(Object::toString).collect(Collectors.joining(","))).append("}"); + logd(builder.toString()); + + // Add this RequestCoordinator to the UceRequestRepository. + addRequestCoordinator(requestCoordinator); + } + + private UceRequestCoordinator createSubscribeRequestCoordinator(final @UceRequestType int type, + final List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) { + SubscribeRequestCoordinator.Builder builder; + + if (!sUceUtilsProxy.isPresenceGroupSubscribeEnabled(mContext, mSubId)) { + // When the group subscribe is disabled, each contact is required to be encapsulated + // into individual UceRequest. + List<UceRequest> requestList = new ArrayList<>(); + uriList.forEach(uri -> { + List<Uri> individualUri = Collections.singletonList(uri); + UceRequest request = createSubscribeRequest(type, individualUri, skipFromCache); + requestList.add(request); + }); + builder = new SubscribeRequestCoordinator.Builder(mSubId, requestList, + mRequestMgrCallback); + builder.setCapabilitiesCallback(callback); + } else { + // Even when the group subscribe is supported by the network, the number of contacts in + // a UceRequest still cannot exceed the maximum. + List<UceRequest> requestList = new ArrayList<>(); + final int rclMaxNumber = sUceUtilsProxy.getRclMaxNumberEntries(mSubId); + int numRequestCoordinators = uriList.size() / rclMaxNumber; + for (int count = 0; count < numRequestCoordinators; count++) { + List<Uri> subUriList = new ArrayList<>(); + for (int index = 0; index < rclMaxNumber; index++) { + subUriList.add(uriList.get(count * rclMaxNumber + index)); + } + requestList.add(createSubscribeRequest(type, subUriList, skipFromCache)); + } + + List<Uri> subUriList = new ArrayList<>(); + for (int i = numRequestCoordinators * rclMaxNumber; i < uriList.size(); i++) { + subUriList.add(uriList.get(i)); + } + requestList.add(createSubscribeRequest(type, subUriList, skipFromCache)); + + builder = new SubscribeRequestCoordinator.Builder(mSubId, requestList, + mRequestMgrCallback); + builder.setCapabilitiesCallback(callback); + } + return builder.build(); + } + + private UceRequestCoordinator createOptionsRequestCoordinator(@UceRequestType int type, + List<Uri> uriList, IRcsUceControllerCallback callback) { + OptionsRequestCoordinator.Builder builder; + List<UceRequest> requestList = new ArrayList<>(); + uriList.forEach(uri -> { + List<Uri> individualUri = Collections.singletonList(uri); + UceRequest request = createOptionsRequest(type, individualUri, false); + requestList.add(request); + }); + builder = new OptionsRequestCoordinator.Builder(mSubId, requestList, mRequestMgrCallback); + builder.setCapabilitiesCallback(callback); + return builder.build(); + } + + private CapabilityRequest createSubscribeRequest(int type, List<Uri> uriList, + boolean skipFromCache) { + CapabilityRequest request = new SubscribeRequest(mSubId, type, mRequestMgrCallback, + mSubscribeCtrl); + request.setContactUri(uriList); + request.setSkipGettingFromCache(skipFromCache); + return request; + } + + private CapabilityRequest createOptionsRequest(int type, List<Uri> uriList, + boolean skipFromCache) { + CapabilityRequest request = new OptionsRequest(mSubId, type, mRequestMgrCallback, + mOptionsCtrl); + request.setContactUri(uriList); + request.setSkipGettingFromCache(skipFromCache); + return request; + } + + /** + * Retrieve the device's capabilities. This request is from the ImsService to send the + * capabilities to the remote side. + */ + public void retrieveCapabilitiesForRemote(Uri contactUri, List<String> remoteCapabilities, + IOptionsRequestCallback requestCallback) { + RemoteOptionsRequest request = new RemoteOptionsRequest(mSubId, mRequestMgrCallback); + request.setContactUri(Collections.singletonList(contactUri)); + request.setRemoteFeatureTags(remoteCapabilities); + + // If the remote number is blocked, do not send capabilities back. + String number = getNumberFromUri(contactUri); + if (!TextUtils.isEmpty(number)) { + request.setIsRemoteNumberBlocked(sUceUtilsProxy.isNumberBlocked(mContext, number)); + } + + // Create the RemoteOptionsCoordinator instance + RemoteOptionsCoordinator.Builder CoordBuilder = new RemoteOptionsCoordinator.Builder( + mSubId, Collections.singletonList(request), mRequestMgrCallback); + CoordBuilder.setOptionsRequestCallback(requestCallback); + RemoteOptionsCoordinator requestCoordinator = CoordBuilder.build(); + + StringBuilder builder = new StringBuilder("retrieveCapabilitiesForRemote: "); + builder.append("requestCoordinatorId ").append(requestCoordinator.getCoordinatorId()) + .append(", taskId={") + .append(requestCoordinator.getActivatedRequestTaskIds().stream() + .map(Object::toString).collect(Collectors.joining(","))).append("}"); + logd(builder.toString()); + + // Add this RequestCoordinator to the UceRequestRepository. + addRequestCoordinator(requestCoordinator); + } + + 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_TIMEOUT = 3; + private static final int EVENT_REQUEST_FINISHED = 4; + private static final int EVENT_COORDINATOR_FINISHED = 5; + + private final Map<Long, SomeArgs> mRequestTimeoutTimers; + private final WeakReference<UceRequestManager> mUceRequestMgrRef; + + public UceRequestHandler(UceRequestManager requestManager, Looper looper) { + super(looper); + mRequestTimeoutTimers = new HashMap<>(); + mUceRequestMgrRef = new WeakReference<>(requestManager); + } + + /** + * Send the capabilities request message. + */ + public void sendRequestMessage(Long coordinatorId, Long taskId, long delayTimeMs) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = coordinatorId; + args.arg2 = taskId; + + Message message = obtainMessage(); + message.what = EVENT_EXECUTE_REQUEST; + message.obj = args; + sendMessageDelayed(message, delayTimeMs); + } + + /** + * Send the Uce request updated message. + */ + public void sendRequestUpdatedMessage(Long coordinatorId, Long taskId, + @UceRequestUpdate int requestEvent) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = coordinatorId; + args.arg2 = taskId; + args.argi1 = requestEvent; + + Message message = obtainMessage(); + message.what = EVENT_REQUEST_UPDATED; + message.obj = args; + 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; + args.arg2 = taskId; + + Message message = obtainMessage(); + message.what = EVENT_REQUEST_FINISHED; + message.obj = args; + sendMessage(message); + } + + /** + * Finish the UceRequestCoordinator associated with the given id. + */ + public void sendRequestCoordinatorFinishedMessage(Long coordinatorId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = coordinatorId; + + Message message = obtainMessage(); + message.what = EVENT_COORDINATOR_FINISHED; + message.obj = args; + sendMessage(message); + } + + /** + * Remove all the messages from the handler + */ + 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 + public void handleMessage(Message msg) { + UceRequestManager requestManager = mUceRequestMgrRef.get(); + if (requestManager == null) { + return; + } + SomeArgs args = (SomeArgs) msg.obj; + final Long coordinatorId = (Long) args.arg1; + final Long taskId = (Long) Optional.ofNullable(args.arg2).orElse(-1L); + final Integer requestEvent = Optional.of(args.argi1).orElse(-1); + args.recycle(); + + requestManager.logd("handleMessage: " + EVENT_DESCRIPTION.get(msg.what) + + ", coordinatorId=" + coordinatorId + ", taskId=" + taskId); + switch (msg.what) { + case EVENT_EXECUTE_REQUEST: { + UceRequest request = requestManager.getUceRequest(taskId); + if (request == null) { + requestManager.logw("handleMessage: cannot find request, taskId=" + taskId); + return; + } + request.executeRequest(); + break; + } + case EVENT_REQUEST_UPDATED: { + UceRequestCoordinator requestCoordinator = + requestManager.getRequestCoordinator(coordinatorId); + if (requestCoordinator == null) { + requestManager.logw("handleMessage: cannot find UceRequestCoordinator"); + return; + } + 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; + } + case EVENT_COORDINATOR_FINISHED: { + UceRequestCoordinator requestCoordinator = + requestManager.removeRequestCoordinator(coordinatorId); + if (requestCoordinator != null) { + requestCoordinator.onFinish(); + } + break; + } + default: { + break; + } + } + } + + private static Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>(); + 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"); + } + } + + private void addRequestCoordinator(UceRequestCoordinator coordinator) { + mRequestRepository.addRequestCoordinator(coordinator); + } + + private UceRequestCoordinator removeRequestCoordinator(Long coordinatorId) { + return mRequestRepository.removeRequestCoordinator(coordinatorId); + } + + private UceRequestCoordinator getRequestCoordinator(Long coordinatorId) { + return mRequestRepository.getRequestCoordinator(coordinatorId); + } + + private UceRequest getUceRequest(Long taskId) { + return mRequestRepository.getUceRequest(taskId); + } + + private void notifyRepositoryRequestFinished(Long taskId) { + mRequestRepository.notifyRequestFinished(taskId); + } + + @VisibleForTesting + public UceRequestHandler getUceRequestHandler() { + return mHandler; + } + + @VisibleForTesting + public RequestManagerCallback getRequestManagerCallback() { + return mRequestMgrCallback; + } + + private void logi(String log) { + Log.i(LOG_TAG, getLogPrefix().append(log).toString()); + } + + private void logd(String log) { + Log.d(LOG_TAG, getLogPrefix().append(log).toString()); + } + + private void logw(String log) { + Log.w(LOG_TAG, getLogPrefix().append(log).toString()); + } + + private StringBuilder getLogPrefix() { + StringBuilder builder = new StringBuilder("["); + builder.append(mSubId); + builder.append("] "); + return builder; + } + + private String getNumberFromUri(Uri uri) { + if (uri == null) return null; + String number = uri.getSchemeSpecificPart(); + String[] numberParts = number.split("[@;:]"); + + if (numberParts.length == 0) { + return null; + } + return numberParts[0]; + } +} |