/* * Copyright (C) 2021 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.util.Log; import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback; import com.android.ims.rcs.uce.util.UceUtils; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; /** * Calculate network carry capabilities and dispatcher the UceRequests. */ public class UceRequestDispatcher { private static final String LOG_TAG = UceUtils.getLogPrefix() + "RequestDispatcher"; /** * Record the request timestamp. */ private static class Request { private final long mTaskId; private final long mCoordinatorId; private Optional mExecutingTime; public Request(long coordinatorId, long taskId) { mTaskId = taskId; mCoordinatorId = coordinatorId; mExecutingTime = Optional.empty(); } public long getCoordinatorId() { return mCoordinatorId; } public long getTaskId() { return mTaskId; } public void setExecutingTime(Instant instant) { mExecutingTime = Optional.of(instant); } public Optional getExecutingTime() { return mExecutingTime; } } private final int mSubId; // The interval milliseconds for each request. private long mIntervalTime = 100; // The number of requests that the network can process at the same time. private int mMaxConcurrentNum = 1; // The collection of all requests waiting to be executed. private final List mWaitingRequests = new ArrayList<>(); // The collection of all executing requests. private final List mExecutingRequests = new ArrayList<>(); // The callback to communicate with UceRequestManager private RequestManagerCallback mRequestManagerCallback; public UceRequestDispatcher(int subId, RequestManagerCallback callback) { mSubId = subId; mRequestManagerCallback = callback; } /** * Clear all the collections when the instance is destroyed. */ public synchronized void onDestroy() { mWaitingRequests.clear(); mExecutingRequests.clear(); mRequestManagerCallback = null; } /** * Add new requests to the waiting collection and trigger sending request if the network is * capable of processing the given requests. */ public synchronized void addRequest(long coordinatorId, List taskIds) { taskIds.stream().forEach(taskId -> { Request request = new Request(coordinatorId, taskId); mWaitingRequests.add(request); }); onRequestUpdated(); } /** * Notify that the request with the given taskId is finished. */ public synchronized void onRequestFinished(Long taskId) { logd("onRequestFinished: taskId=" + taskId); mExecutingRequests.removeIf(request -> request.getTaskId() == taskId); onRequestUpdated(); } private synchronized void onRequestUpdated() { logd("onRequestUpdated: waiting=" + mWaitingRequests.size() + ", executing=" + mExecutingRequests.size()); // Return if there is no waiting request. if (mWaitingRequests.isEmpty()) { return; } // Check how many more requests can be executed and return if the size of executing // requests have reached the maximum number. int numCapacity = mMaxConcurrentNum - mExecutingRequests.size(); if (numCapacity <= 0) { return; } List requestList = getRequestFromWaitingCollection(numCapacity); if (!requestList.isEmpty()) { notifyStartOfRequest(requestList); } } /* * Retrieve the given number of requests from the WaitingRequestList. */ private List getRequestFromWaitingCollection(int numCapacity) { // The number of the requests cannot more than the waiting requests. int numRequests = (numCapacity < mWaitingRequests.size()) ? numCapacity : mWaitingRequests.size(); List requestList = new ArrayList<>(); for (int i = 0; i < numRequests; i++) { requestList.add(mWaitingRequests.get(i)); } mWaitingRequests.removeAll(requestList); return requestList; } /** * Notify start of the UceRequest. */ private void notifyStartOfRequest(List requestList) { RequestManagerCallback callback = mRequestManagerCallback; if (callback == null) { logd("notifyStartOfRequest: The instance is destroyed"); return; } Instant lastRequestTime = getLastRequestTime(); Instant baseTime; if (lastRequestTime.plusMillis(mIntervalTime).isAfter(Instant.now())) { baseTime = lastRequestTime.plusMillis(mIntervalTime); } else { baseTime = Instant.now(); } StringBuilder builder = new StringBuilder("notifyStartOfRequest: taskId="); for (int i = 0; i < requestList.size(); i++) { Instant startExecutingTime = baseTime.plusMillis((mIntervalTime * i)); Request request = requestList.get(i); request.setExecutingTime(startExecutingTime); // Add the request to the executing collection mExecutingRequests.add(request); // Notify RequestManager to execute this task. long taskId = request.getTaskId(); long coordId = request.getCoordinatorId(); long delayTime = getDelayTime(startExecutingTime); mRequestManagerCallback.notifySendingRequest(coordId, taskId, delayTime); builder.append(request.getTaskId() + ", "); } builder.append("ExecutingRequests size=" + mExecutingRequests.size()); logd(builder.toString()); } private Instant getLastRequestTime() { if (mExecutingRequests.isEmpty()) { return Instant.MIN; } Instant lastTime = Instant.MIN; for (Request request : mExecutingRequests) { if (!request.getExecutingTime().isPresent()) continue; Instant executingTime = request.getExecutingTime().get(); if (executingTime.isAfter(lastTime)) { lastTime = executingTime; } } return lastTime; } private long getDelayTime(Instant executingTime) { long delayTime = Duration.between(executingTime, Instant.now()).toMillis(); if (delayTime < 0L) { delayTime = 0; } return delayTime; } private void logd(String log) { Log.d(LOG_TAG, getLogPrefix().append(log).toString()); } private StringBuilder getLogPrefix() { StringBuilder builder = new StringBuilder("["); builder.append(mSubId); builder.append("] "); return builder; } }