1 /*
2  * Copyright (C) 2020 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.presence.publish;
18 
19 import android.util.Log;
20 
21 import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType;
22 import com.android.ims.rcs.uce.util.UceUtils;
23 
24 import java.time.Duration;
25 import java.time.Instant;
26 import java.time.temporal.ChronoUnit;
27 import java.util.Optional;
28 import java.util.concurrent.TimeUnit;
29 
30 /**
31  * The helper class to manage the publish request parameters.
32  */
33 public class PublishProcessorState {
34 
35     private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishProcessorState";
36 
37     /*
38      * Manager the pending request flag and the trigger type of this pending request.
39      */
40     private static class PendingRequest {
41         private boolean mPendingFlag;
42         private Optional<Integer> mTriggerType;
43         private final Object mLock = new Object();
44 
PendingRequest()45         public PendingRequest() {
46             mTriggerType = Optional.empty();
47         }
48 
49         // Set the flag to indicate there is a pending request.
setPendingRequest(@ublishTriggerType int triggerType)50         public void setPendingRequest(@PublishTriggerType int triggerType) {
51             synchronized (mLock) {
52                 mPendingFlag = true;
53                 mTriggerType = Optional.of(triggerType);
54             }
55         }
56 
57         // Clear the flag. The publish request is triggered and this flag can be cleared.
clearPendingRequest()58         public void clearPendingRequest() {
59             synchronized (mLock) {
60                 mPendingFlag = false;
61                 mTriggerType = Optional.empty();
62             }
63         }
64 
65         // Check if there is pending request need to be executed.
hasPendingRequest()66         public boolean hasPendingRequest() {
67             synchronized (mLock) {
68                 return mPendingFlag;
69             }
70         }
71 
72         // Get the trigger type of the pending request.
getPendingRequestTriggerType()73         public Optional<Integer> getPendingRequestTriggerType() {
74             synchronized (mLock) {
75                 return mTriggerType;
76             }
77         }
78     }
79 
80     /**
81      * Manager when the PUBLISH request can be executed.
82      */
83     private static class PublishThrottle {
84         // The unit time interval of the request retry.
85         private static final int RETRY_BASE_PERIOD_MIN = 1;
86 
87         // The maximum number of the publication retries.
88         private static final int PUBLISH_MAXIMUM_NUM_RETRIES = 3;
89 
90         // Get the minimum time that allows two PUBLISH requests can be executed continuously.
91         // It is one of the calculation conditions for the next publish allowed time.
92         private long mRcsPublishThrottle;
93 
94         // The number of times the PUBLISH failed to retry. It is one of the calculation conditions
95         // for the next publish allowed time.
96         private int mRetryCount;
97 
98         // The subscription ID associated with this throttle helper.
99         private int mSubId;
100 
101         // The time when the last PUBLISH request is success. It is one of the calculation
102         // conditions for the next publish allowed time.
103         private Optional<Instant> mLastPublishedTime;
104 
105         // The time to allow to execute the publishing request.
106         private Optional<Instant> mPublishAllowedTime;
107 
PublishThrottle(int subId)108         public PublishThrottle(int subId) {
109             mSubId = subId;
110             resetState();
111         }
112 
113         // Set the time of the last successful PUBLISH request.
setLastPublishedTime(Instant lastPublishedTime)114         public void setLastPublishedTime(Instant lastPublishedTime) {
115             mLastPublishedTime = Optional.of(lastPublishedTime);
116         }
117 
118         // Increase the retry count when the PUBLISH has failed and need to be retried.
increaseRetryCount()119         public void increaseRetryCount() {
120             if (mRetryCount < PUBLISH_MAXIMUM_NUM_RETRIES) {
121                 mRetryCount++;
122             }
123             // Adjust the publish allowed time.
124             calcLatestPublishAllowedTime();
125         }
126 
127         // Reset the retry count when the PUBLISH request is success or it does not need to retry.
resetRetryCount()128         public void resetRetryCount() {
129             mRetryCount = 0;
130             // Adjust the publish allowed time.
131             calcLatestPublishAllowedTime();
132         }
133 
134         // In the case that the ImsService is disconnected, reset state for when the service
135         // reconnects
resetState()136         public void resetState() {
137             mLastPublishedTime = Optional.empty();
138             mPublishAllowedTime = Optional.empty();
139             mRcsPublishThrottle = UceUtils.getRcsPublishThrottle(mSubId);
140             Log.d(LOG_TAG, "RcsPublishThrottle=" + mRcsPublishThrottle);
141         }
142 
143         // Check if it has reached the maximum retries.
isReachMaximumRetries()144         public boolean isReachMaximumRetries() {
145             return (mRetryCount >= PUBLISH_MAXIMUM_NUM_RETRIES) ? true : false;
146         }
147 
148         // Update the RCS publish throttle
updatePublishThrottle(int publishThrottle)149         public void updatePublishThrottle(int publishThrottle) {
150             mRcsPublishThrottle = publishThrottle;
151             calcLatestPublishAllowedTime();
152         }
153 
154         // Check if the PUBLISH request can be executed now.
isPublishAllowedAtThisTime()155         public boolean isPublishAllowedAtThisTime() {
156             // If the allowed time has not been set, it means that it is not ready to PUBLISH.
157             // It means that it has not received the publish request from the service.
158             if (!mPublishAllowedTime.isPresent()) {
159                 return false;
160             }
161 
162             // Check whether the current time has exceeded the allowed PUBLISH.
163             return (Instant.now().isBefore(mPublishAllowedTime.get())) ? false : true;
164         }
165 
166         // Update the PUBLISH allowed time with the given trigger type.
updatePublishingAllowedTime(@ublishTriggerType int triggerType)167         public void updatePublishingAllowedTime(@PublishTriggerType int triggerType) {
168             if (triggerType == PublishController.PUBLISH_TRIGGER_SERVICE) {
169                 // If the request is triggered by service, reset the retry count and allow to
170                 // execute the PUBLISH immediately.
171                 mRetryCount = 0;
172                 mPublishAllowedTime = Optional.of(Instant.now());
173             } else if (triggerType != PublishController.PUBLISH_TRIGGER_RETRY) {
174                 // If the trigger type is not RETRY, it means that the device capabilities have
175                 // changed, reset the retry cout.
176                 resetRetryCount();
177             }
178         }
179 
180         // Get the delay time to allow to execute the PUBLISH request.
getPublishingDelayTime()181         public Optional<Long> getPublishingDelayTime() {
182             // If the allowed time has not been set, it means that it is not ready to PUBLISH.
183             // It means that it has not received the publish request from the service.
184             if (!mPublishAllowedTime.isPresent()) {
185                 return Optional.empty();
186             }
187 
188             // Setup the delay to the time which publish request is allowed to be executed.
189             long delayTime = ChronoUnit.MILLIS.between(Instant.now(), mPublishAllowedTime.get());
190             if (delayTime < 0) {
191                 delayTime = 0L;
192             }
193             return Optional.of(delayTime);
194         }
195 
196         // Calculate the latest time allowed to PUBLISH
calcLatestPublishAllowedTime()197         private void calcLatestPublishAllowedTime() {
198             final long retryDelay = getNextRetryDelayTime();
199             if (!mLastPublishedTime.isPresent()) {
200                 // If the publish request has not been successful before, it does not need to
201                 // consider the PUBLISH throttle. The publish allowed time is decided by the retry
202                 // delay.
203                 mPublishAllowedTime = Optional.of(
204                         Instant.now().plus(Duration.ofMillis(retryDelay)));
205                 Log.d(LOG_TAG, "calcLatestPublishAllowedTime: The last published time is empty");
206             } else {
207                 // The default allowed time is the last published successful time plus the
208                 // PUBLISH throttle.
209                 Instant lastPublishedTime = mLastPublishedTime.get();
210                 Instant defaultAllowedTime = lastPublishedTime.plus(
211                         Duration.ofMillis(mRcsPublishThrottle));
212 
213                 if (retryDelay == 0) {
214                     // If there is no delay time, the default allowed time is used.
215                     mPublishAllowedTime = Optional.of(defaultAllowedTime);
216                 } else {
217                     // When the retry count is updated and there is delay time, it needs to compare
218                     // the default time and the retry delay time. The later time will be the
219                     // final decision value.
220                     Instant retryDelayTime = Instant.now().plus(Duration.ofMillis(retryDelay));
221                     mPublishAllowedTime = Optional.of(
222                             (retryDelayTime.isAfter(defaultAllowedTime))
223                                     ? retryDelayTime : defaultAllowedTime);
224                 }
225             }
226             Log.d(LOG_TAG, "calcLatestPublishAllowedTime: " + mPublishAllowedTime.get());
227         }
228 
229         // Get the milliseconds of the next retry delay.
getNextRetryDelayTime()230         private long getNextRetryDelayTime() {
231             // If the current retry count is zero, the delay time is also zero.
232             if (mRetryCount == 0) return 0L;
233             // Next retry delay time (minute)
234             int power = mRetryCount - 1;
235             Double delayTime = RETRY_BASE_PERIOD_MIN * Math.pow(2, power);
236             // Convert to millis
237             return TimeUnit.MINUTES.toMillis(delayTime.longValue());
238         }
239     }
240 
241 
242     private long mTaskId;
243 
244     // Used to check whether the publish request is running now.
245     private volatile boolean mIsPublishing;
246 
247     // Control the pending request flag.
248     private final PendingRequest mPendingRequest;
249 
250     // Control the publish throttle
251     private final PublishThrottle mPublishThrottle;
252 
253     private final Object mLock = new Object();
254 
PublishProcessorState(int subId)255     public PublishProcessorState(int subId) {
256         mPendingRequest = new PendingRequest();
257         mPublishThrottle = new PublishThrottle(subId);
258     }
259 
260     /**
261      * @return A unique task Id for this request.
262      */
generatePublishTaskId()263     public long generatePublishTaskId() {
264         synchronized (mLock) {
265             mTaskId = UceUtils.generateTaskId();
266             return mTaskId;
267         }
268     }
269 
270     /**
271      * @return The current valid PUBLISH task ID.
272      */
getCurrentTaskId()273     public long getCurrentTaskId() {
274         synchronized (mLock) {
275             return mTaskId;
276         }
277     }
278 
279     /**
280      * Set the publishing flag to indicate whether it is executing a PUBLISH request or not.
281      */
setPublishingFlag(boolean flag)282     public void setPublishingFlag(boolean flag) {
283         mIsPublishing = flag;
284     }
285 
286     /**
287      * @return true if it is executing a PUBLISH request now.
288      */
isPublishingNow()289     public boolean isPublishingNow() {
290         return mIsPublishing;
291     }
292 
293     /**
294      * Set the flag to indicate there is a pending request waiting to be executed.
295      */
setPendingRequest(@ublishTriggerType int triggerType)296     public void setPendingRequest(@PublishTriggerType int triggerType) {
297         mPendingRequest.setPendingRequest(triggerType);
298     }
299 
300     /**
301      * Clear the flag. It means a new publish request is triggered and the pending request flag
302      * can be cleared.
303      */
clearPendingRequest()304     public void clearPendingRequest() {
305         mPendingRequest.clearPendingRequest();
306     }
307 
308     /**
309      * @return true if there is pending request to be executed.
310      */
hasPendingRequest()311     public boolean hasPendingRequest() {
312         return mPendingRequest.hasPendingRequest();
313     }
314 
315     /**
316      * @return The trigger type of the pending request. If there is no pending request, it will
317      * return Optional.empty
318      */
getPendingRequestTriggerType()319     public Optional<Integer> getPendingRequestTriggerType() {
320         return mPendingRequest.getPendingRequestTriggerType();
321     }
322 
323     /**
324      * Set the time of the last successful PUBLISH request.
325      * @param lastPublishedTime The time when the last PUBLISH request is success
326      */
setLastPublishedTime(Instant lastPublishedTime)327     public void setLastPublishedTime(Instant lastPublishedTime) {
328         synchronized (mLock) {
329             mPublishThrottle.setLastPublishedTime(lastPublishedTime);
330         }
331     }
332 
333     /**
334      * Increase the retry count when the PUBLISH has failed and need to retry.
335      */
increaseRetryCount()336     public void increaseRetryCount() {
337         synchronized (mLock) {
338             mPublishThrottle.increaseRetryCount();
339         }
340     }
341 
342     /**
343      * Reset the retry count when the PUBLISH request is success or it does not need to retry.
344      */
resetRetryCount()345     public void resetRetryCount() {
346         synchronized (mLock) {
347             mPublishThrottle.resetRetryCount();
348         }
349     }
350 
351     /**
352      * Reset the retry count and related time when the publication status has
353      * changed to not_published.
354      */
resetState()355     public void resetState() {
356         synchronized (mLock) {
357             mPublishThrottle.resetState();
358         }
359     }
360 
361     /*
362      * Check if it has reached the maximum retry count.
363      */
isReachMaximumRetries()364     public boolean isReachMaximumRetries() {
365         synchronized (mLock) {
366             return mPublishThrottle.isReachMaximumRetries();
367         }
368     }
369 
370     /*
371      * Check if the PUBLISH can be executed now.
372      */
isPublishAllowedAtThisTime()373     public boolean isPublishAllowedAtThisTime() {
374         synchronized (mLock) {
375             return mPublishThrottle.isPublishAllowedAtThisTime();
376         }
377     }
378 
379     /**
380      * Update the PUBLISH allowed time with the given trigger type.
381      * @param triggerType The trigger type of this PUBLISH request
382      */
updatePublishingAllowedTime(@ublishTriggerType int triggerType)383     public void updatePublishingAllowedTime(@PublishTriggerType int triggerType) {
384         synchronized (mLock) {
385             mPublishThrottle.updatePublishingAllowedTime(triggerType);
386         }
387     }
388 
389     // Get the delay time to allow to execute the PUBLISH request.
getPublishingDelayTime()390     public Optional<Long> getPublishingDelayTime() {
391         synchronized (mLock) {
392             return mPublishThrottle.getPublishingDelayTime();
393         }
394     }
395 
updatePublishThrottle(int publishThrottle)396     public void updatePublishThrottle(int publishThrottle) {
397         synchronized (mLock) {
398             mPublishThrottle.updatePublishThrottle(publishThrottle);
399         }
400     }
401 
onRcsDisconnected()402     public void onRcsDisconnected() {
403         synchronized (mLock) {
404             setPublishingFlag(false /*isPublishing*/);
405             clearPendingRequest();
406             mPublishThrottle.resetState();
407         }
408     }
409 }
410