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 android.util.Log;
20 
21 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback;
22 import com.android.ims.rcs.uce.util.UceUtils;
23 
24 import java.time.Duration;
25 import java.time.Instant;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Optional;
29 
30 /**
31  * Calculate network carry capabilities and dispatcher the UceRequests.
32  */
33 public class UceRequestDispatcher {
34 
35     private static final String LOG_TAG = UceUtils.getLogPrefix() + "RequestDispatcher";
36 
37     /**
38      * Record the request timestamp.
39      */
40     private static class Request {
41         private final long mTaskId;
42         private final long mCoordinatorId;
43         private Optional<Instant> mExecutingTime;
44 
Request(long coordinatorId, long taskId)45         public Request(long coordinatorId, long taskId) {
46             mTaskId = taskId;
47             mCoordinatorId = coordinatorId;
48             mExecutingTime = Optional.empty();
49         }
50 
getCoordinatorId()51         public long getCoordinatorId() {
52             return mCoordinatorId;
53         }
54 
getTaskId()55         public long getTaskId() {
56             return mTaskId;
57         }
58 
setExecutingTime(Instant instant)59         public void setExecutingTime(Instant instant) {
60             mExecutingTime = Optional.of(instant);
61         }
62 
getExecutingTime()63         public Optional<Instant> getExecutingTime() {
64             return mExecutingTime;
65         }
66     }
67 
68     private final int mSubId;
69 
70     // The interval milliseconds for each request.
71     private long mIntervalTime = 100;
72 
73     // The number of requests that the network can process at the same time.
74     private int mMaxConcurrentNum = 1;
75 
76     // The collection of all requests waiting to be executed.
77     private final List<Request> mWaitingRequests = new ArrayList<>();
78 
79     // The collection of all executing requests.
80     private final List<Request> mExecutingRequests = new ArrayList<>();
81 
82     // The callback to communicate with UceRequestManager
83     private RequestManagerCallback mRequestManagerCallback;
84 
UceRequestDispatcher(int subId, RequestManagerCallback callback)85     public UceRequestDispatcher(int subId, RequestManagerCallback callback) {
86         mSubId = subId;
87         mRequestManagerCallback = callback;
88     }
89 
90     /**
91      * Clear all the collections when the instance is destroyed.
92      */
onDestroy()93     public synchronized void onDestroy() {
94         mWaitingRequests.clear();
95         mExecutingRequests.clear();
96         mRequestManagerCallback = null;
97     }
98 
99     /**
100      * Add new requests to the waiting collection and trigger sending request if the network is
101      * capable of processing the given requests.
102      */
addRequest(long coordinatorId, List<Long> taskIds)103     public synchronized void addRequest(long coordinatorId, List<Long> taskIds) {
104         taskIds.stream().forEach(taskId -> {
105             Request request = new Request(coordinatorId, taskId);
106             mWaitingRequests.add(request);
107         });
108         onRequestUpdated();
109     }
110 
111     /**
112      * Notify that the request with the given taskId is finished.
113      */
onRequestFinished(Long taskId)114     public synchronized void onRequestFinished(Long taskId) {
115         logd("onRequestFinished: taskId=" + taskId);
116         mExecutingRequests.removeIf(request -> request.getTaskId() == taskId);
117         onRequestUpdated();
118     }
119 
onRequestUpdated()120     private synchronized void onRequestUpdated() {
121         logd("onRequestUpdated: waiting=" + mWaitingRequests.size()
122                 + ", executing=" + mExecutingRequests.size());
123 
124         // Return if there is no waiting request.
125         if (mWaitingRequests.isEmpty()) {
126             return;
127         }
128 
129         // Check how many more requests can be executed and return if the size of executing
130         // requests have reached the maximum number.
131         int numCapacity = mMaxConcurrentNum - mExecutingRequests.size();
132         if (numCapacity <= 0) {
133             return;
134         }
135 
136         List<Request> requestList = getRequestFromWaitingCollection(numCapacity);
137         if (!requestList.isEmpty()) {
138             notifyStartOfRequest(requestList);
139         }
140     }
141 
142     /*
143      * Retrieve the given number of requests from the WaitingRequestList.
144      */
getRequestFromWaitingCollection(int numCapacity)145     private List<Request> getRequestFromWaitingCollection(int numCapacity) {
146         // The number of the requests cannot more than the waiting requests.
147         int numRequests = (numCapacity < mWaitingRequests.size()) ?
148                 numCapacity : mWaitingRequests.size();
149 
150         List<Request> requestList = new ArrayList<>();
151         for (int i = 0; i < numRequests; i++) {
152             requestList.add(mWaitingRequests.get(i));
153         }
154 
155         mWaitingRequests.removeAll(requestList);
156         return requestList;
157     }
158 
159     /**
160      * Notify start of the UceRequest.
161      */
notifyStartOfRequest(List<Request> requestList)162     private void notifyStartOfRequest(List<Request> requestList) {
163         RequestManagerCallback callback = mRequestManagerCallback;
164         if (callback == null) {
165             logd("notifyStartOfRequest: The instance is destroyed");
166             return;
167         }
168 
169         Instant lastRequestTime = getLastRequestTime();
170         Instant baseTime;
171         if (lastRequestTime.plusMillis(mIntervalTime).isAfter(Instant.now())) {
172             baseTime = lastRequestTime.plusMillis(mIntervalTime);
173         } else {
174             baseTime = Instant.now();
175         }
176 
177         StringBuilder builder = new StringBuilder("notifyStartOfRequest: taskId=");
178         for (int i = 0; i < requestList.size(); i++) {
179             Instant startExecutingTime = baseTime.plusMillis((mIntervalTime * i));
180             Request request = requestList.get(i);
181             request.setExecutingTime(startExecutingTime);
182 
183             // Add the request to the executing collection
184             mExecutingRequests.add(request);
185 
186             // Notify RequestManager to execute this task.
187             long taskId = request.getTaskId();
188             long coordId = request.getCoordinatorId();
189             long delayTime = getDelayTime(startExecutingTime);
190             mRequestManagerCallback.notifySendingRequest(coordId, taskId, delayTime);
191 
192             builder.append(request.getTaskId() + ", ");
193         }
194         builder.append("ExecutingRequests size=" + mExecutingRequests.size());
195         logd(builder.toString());
196     }
197 
getLastRequestTime()198     private Instant getLastRequestTime() {
199         if (mExecutingRequests.isEmpty()) {
200             return Instant.MIN;
201         }
202 
203         Instant lastTime = Instant.MIN;
204         for (Request request : mExecutingRequests) {
205             if (!request.getExecutingTime().isPresent()) continue;
206             Instant executingTime = request.getExecutingTime().get();
207             if (executingTime.isAfter(lastTime)) {
208                 lastTime = executingTime;
209             }
210         }
211         return lastTime;
212     }
213 
getDelayTime(Instant executingTime)214     private long getDelayTime(Instant executingTime) {
215         long delayTime = Duration.between(executingTime, Instant.now()).toMillis();
216         if (delayTime < 0L) {
217             delayTime = 0;
218         }
219         return delayTime;
220     }
221 
logd(String log)222     private void logd(String log) {
223         Log.d(LOG_TAG, getLogPrefix().append(log).toString());
224     }
225 
getLogPrefix()226     private StringBuilder getLogPrefix() {
227         StringBuilder builder = new StringBuilder("[");
228         builder.append(mSubId);
229         builder.append("] ");
230         return builder;
231     }
232 }
233 
234