aboutsummaryrefslogtreecommitdiff
path: root/src/java/com/android/ims/rcs/uce/presence/publish/PublishProcessorState.java
blob: 40d901f60c2cba70a5b7105c823d752cd23903a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/*
 * 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.presence.publish;

import android.util.Log;

import com.android.ims.rcs.uce.presence.publish.PublishController.PublishTriggerType;
import com.android.ims.rcs.uce.util.UceUtils;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * The helper class to manage the publish request parameters.
 */
public class PublishProcessorState {

    private static final String LOG_TAG = UceUtils.getLogPrefix() + "PublishProcessorState";

    /*
     * Manager the pending request flag and the trigger type of this pending request.
     */
    private static class PendingRequest {
        private boolean mPendingFlag;
        private Optional<Integer> mTriggerType;
        private final Object mLock = new Object();

        public PendingRequest() {
            mTriggerType = Optional.empty();
        }

        // Set the flag to indicate there is a pending request.
        public void setPendingRequest(@PublishTriggerType int triggerType) {
            synchronized (mLock) {
                mPendingFlag = true;
                mTriggerType = Optional.of(triggerType);
            }
        }

        // Clear the flag. The publish request is triggered and this flag can be cleared.
        public void clearPendingRequest() {
            synchronized (mLock) {
                mPendingFlag = false;
                mTriggerType = Optional.empty();
            }
        }

        // Check if there is pending request need to be executed.
        public boolean hasPendingRequest() {
            synchronized (mLock) {
                return mPendingFlag;
            }
        }

        // Get the trigger type of the pending request.
        public Optional<Integer> getPendingRequestTriggerType() {
            synchronized (mLock) {
                return mTriggerType;
            }
        }
    }

    /**
     * Manager when the PUBLISH request can be executed.
     */
    private static class PublishThrottle {
        // The unit time interval of the request retry.
        private static final int RETRY_BASE_PERIOD_MIN = 1;

        // The maximum number of the publication retries.
        private static final int PUBLISH_MAXIMUM_NUM_RETRIES = 3;

        // Get the minimum time that allows two PUBLISH requests can be executed continuously.
        // It is one of the calculation conditions for the next publish allowed time.
        private long mRcsPublishThrottle;

        // The number of times the PUBLISH failed to retry. It is one of the calculation conditions
        // for the next publish allowed time.
        private int mRetryCount;

        // The subscription ID associated with this throttle helper.
        private int mSubId;

        // The time when the last PUBLISH request is success. It is one of the calculation
        // conditions for the next publish allowed time.
        private Optional<Instant> mLastPublishedTime;

        // The time to allow to execute the publishing request.
        private Optional<Instant> mPublishAllowedTime;

        public PublishThrottle(int subId) {
            mSubId = subId;
            resetState();
        }

        // Set the time of the last successful PUBLISH request.
        public void setLastPublishedTime(Instant lastPublishedTime) {
            mLastPublishedTime = Optional.of(lastPublishedTime);
        }

        // Increase the retry count when the PUBLISH has failed and need to be retried.
        public void increaseRetryCount() {
            if (mRetryCount < PUBLISH_MAXIMUM_NUM_RETRIES) {
                mRetryCount++;
            }
            // Adjust the publish allowed time.
            calcLatestPublishAllowedTime();
        }

        // Reset the retry count when the PUBLISH request is success or it does not need to retry.
        public void resetRetryCount() {
            mRetryCount = 0;
            // Adjust the publish allowed time.
            calcLatestPublishAllowedTime();
        }

        // In the case that the ImsService is disconnected, reset state for when the service
        // reconnects
        public void resetState() {
            mLastPublishedTime = Optional.empty();
            mPublishAllowedTime = Optional.empty();
            mRcsPublishThrottle = UceUtils.getRcsPublishThrottle(mSubId);
            Log.d(LOG_TAG, "RcsPublishThrottle=" + mRcsPublishThrottle);
        }

        // Check if it has reached the maximum retries.
        public boolean isReachMaximumRetries() {
            return (mRetryCount >= PUBLISH_MAXIMUM_NUM_RETRIES) ? true : false;
        }

        // Update the RCS publish throttle
        public void updatePublishThrottle(int publishThrottle) {
            mRcsPublishThrottle = publishThrottle;
            calcLatestPublishAllowedTime();
        }

        // Check if the PUBLISH request can be executed now.
        public boolean isPublishAllowedAtThisTime() {
            // If the allowed time has not been set, it means that it is not ready to PUBLISH.
            // It means that it has not received the publish request from the service.
            if (!mPublishAllowedTime.isPresent()) {
                return false;
            }

            // Check whether the current time has exceeded the allowed PUBLISH.
            return (Instant.now().isBefore(mPublishAllowedTime.get())) ? false : true;
        }

        // Update the PUBLISH allowed time with the given trigger type.
        public void updatePublishingAllowedTime(@PublishTriggerType int triggerType) {
            if (triggerType == PublishController.PUBLISH_TRIGGER_SERVICE) {
                // If the request is triggered by service, reset the retry count and allow to
                // execute the PUBLISH immediately.
                mRetryCount = 0;
                mPublishAllowedTime = Optional.of(Instant.now());
            } else if (triggerType != PublishController.PUBLISH_TRIGGER_RETRY) {
                // If the trigger type is not RETRY, it means that the device capabilities have
                // changed, reset the retry cout.
                resetRetryCount();
            }
        }

        // Get the delay time to allow to execute the PUBLISH request.
        public Optional<Long> getPublishingDelayTime() {
            // If the allowed time has not been set, it means that it is not ready to PUBLISH.
            // It means that it has not received the publish request from the service.
            if (!mPublishAllowedTime.isPresent()) {
                return Optional.empty();
            }

            // Setup the delay to the time which publish request is allowed to be executed.
            long delayTime = ChronoUnit.MILLIS.between(Instant.now(), mPublishAllowedTime.get());
            if (delayTime < 0) {
                delayTime = 0L;
            }
            return Optional.of(delayTime);
        }

        // Calculate the latest time allowed to PUBLISH
        private void calcLatestPublishAllowedTime() {
            final long retryDelay = getNextRetryDelayTime();
            if (!mLastPublishedTime.isPresent()) {
                // If the publish request has not been successful before, it does not need to
                // consider the PUBLISH throttle. The publish allowed time is decided by the retry
                // delay.
                mPublishAllowedTime = Optional.of(
                        Instant.now().plus(Duration.ofMillis(retryDelay)));
                Log.d(LOG_TAG, "calcLatestPublishAllowedTime: The last published time is empty");
            } else {
                // The default allowed time is the last published successful time plus the
                // PUBLISH throttle.
                Instant lastPublishedTime = mLastPublishedTime.get();
                Instant defaultAllowedTime = lastPublishedTime.plus(
                        Duration.ofMillis(mRcsPublishThrottle));

                if (retryDelay == 0) {
                    // If there is no delay time, the default allowed time is used.
                    mPublishAllowedTime = Optional.of(defaultAllowedTime);
                } else {
                    // When the retry count is updated and there is delay time, it needs to compare
                    // the default time and the retry delay time. The later time will be the
                    // final decision value.
                    Instant retryDelayTime = Instant.now().plus(Duration.ofMillis(retryDelay));
                    mPublishAllowedTime = Optional.of(
                            (retryDelayTime.isAfter(defaultAllowedTime))
                                    ? retryDelayTime : defaultAllowedTime);
                }
            }
            Log.d(LOG_TAG, "calcLatestPublishAllowedTime: " + mPublishAllowedTime.get());
        }

        // Get the milliseconds of the next retry delay.
        private long getNextRetryDelayTime() {
            // If the current retry count is zero, the delay time is also zero.
            if (mRetryCount == 0) return 0L;
            // Next retry delay time (minute)
            int power = mRetryCount - 1;
            Double delayTime = RETRY_BASE_PERIOD_MIN * Math.pow(2, power);
            // Convert to millis
            return TimeUnit.MINUTES.toMillis(delayTime.longValue());
        }
    }


    private long mTaskId;

    // Used to check whether the publish request is running now.
    private volatile boolean mIsPublishing;

    // Control the pending request flag.
    private final PendingRequest mPendingRequest;

    // Control the publish throttle
    private final PublishThrottle mPublishThrottle;

    private final Object mLock = new Object();

    public PublishProcessorState(int subId) {
        mPendingRequest = new PendingRequest();
        mPublishThrottle = new PublishThrottle(subId);
    }

    /**
     * @return A unique task Id for this request.
     */
    public long generatePublishTaskId() {
        synchronized (mLock) {
            mTaskId = UceUtils.generateTaskId();
            return mTaskId;
        }
    }

    /**
     * @return The current valid PUBLISH task ID.
     */
    public long getCurrentTaskId() {
        synchronized (mLock) {
            return mTaskId;
        }
    }

    /**
     * Set the publishing flag to indicate whether it is executing a PUBLISH request or not.
     */
    public void setPublishingFlag(boolean flag) {
        mIsPublishing = flag;
    }

    /**
     * @return true if it is executing a PUBLISH request now.
     */
    public boolean isPublishingNow() {
        return mIsPublishing;
    }

    /**
     * Set the flag to indicate there is a pending request waiting to be executed.
     */
    public void setPendingRequest(@PublishTriggerType int triggerType) {
        mPendingRequest.setPendingRequest(triggerType);
    }

    /**
     * Clear the flag. It means a new publish request is triggered and the pending request flag
     * can be cleared.
     */
    public void clearPendingRequest() {
        mPendingRequest.clearPendingRequest();
    }

    /**
     * @return true if there is pending request to be executed.
     */
    public boolean hasPendingRequest() {
        return mPendingRequest.hasPendingRequest();
    }

    /**
     * @return The trigger type of the pending request. If there is no pending request, it will
     * return Optional.empty
     */
    public Optional<Integer> getPendingRequestTriggerType() {
        return mPendingRequest.getPendingRequestTriggerType();
    }

    /**
     * Set the time of the last successful PUBLISH request.
     * @param lastPublishedTime The time when the last PUBLISH request is success
     */
    public void setLastPublishedTime(Instant lastPublishedTime) {
        synchronized (mLock) {
            mPublishThrottle.setLastPublishedTime(lastPublishedTime);
        }
    }

    /**
     * Increase the retry count when the PUBLISH has failed and need to retry.
     */
    public void increaseRetryCount() {
        synchronized (mLock) {
            mPublishThrottle.increaseRetryCount();
        }
    }

    /**
     * Reset the retry count when the PUBLISH request is success or it does not need to retry.
     */
    public void resetRetryCount() {
        synchronized (mLock) {
            mPublishThrottle.resetRetryCount();
        }
    }

    /*
     * Check if it has reached the maximum retry count.
     */
    public boolean isReachMaximumRetries() {
        synchronized (mLock) {
            return mPublishThrottle.isReachMaximumRetries();
        }
    }

    /*
     * Check if the PUBLISH can be executed now.
     */
    public boolean isPublishAllowedAtThisTime() {
        synchronized (mLock) {
            return mPublishThrottle.isPublishAllowedAtThisTime();
        }
    }

    /**
     * Update the PUBLISH allowed time with the given trigger type.
     * @param triggerType The trigger type of this PUBLISH request
     */
    public void updatePublishingAllowedTime(@PublishTriggerType int triggerType) {
        synchronized (mLock) {
            mPublishThrottle.updatePublishingAllowedTime(triggerType);
        }
    }

    // Get the delay time to allow to execute the PUBLISH request.
    public Optional<Long> getPublishingDelayTime() {
        synchronized (mLock) {
            return mPublishThrottle.getPublishingDelayTime();
        }
    }

    public void updatePublishThrottle(int publishThrottle) {
        synchronized (mLock) {
            mPublishThrottle.updatePublishThrottle(publishThrottle);
        }
    }

    public void onRcsDisconnected() {
        synchronized (mLock) {
            setPublishingFlag(false /*isPublishing*/);
            clearPendingRequest();
            mPublishThrottle.resetState();
        }
    }
}