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;
18 
19 import android.annotation.IntDef;
20 import android.content.Context;
21 import android.telephony.ims.RcsUceAdapter.ErrorCode;
22 import android.util.Log;
23 
24 import com.android.ims.rcs.uce.UceController.RequestType;
25 import com.android.ims.rcs.uce.UceController.UceControllerCallback;
26 import com.android.ims.rcs.uce.util.NetworkSipCode;
27 import com.android.ims.rcs.uce.util.UceUtils;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 import java.time.Instant;
32 import java.time.temporal.ChronoUnit;
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.Optional;
36 
37 /**
38  * Manager the device state to determine whether the device is allowed to execute UCE requests or
39  * not.
40  */
41 public class UceDeviceState {
42 
43     private static final String LOG_TAG = UceUtils.getLogPrefix() + "UceDeviceState";
44 
45     /**
46      * The device is allowed to execute UCE requests.
47      */
48     private static final int DEVICE_STATE_OK = 0;
49 
50     /**
51      * The device will be in the forbidden state when the network response SIP code is 403
52      */
53     private static final int DEVICE_STATE_FORBIDDEN = 1;
54 
55     /**
56      * The device will be in the PROVISION error state when the PUBLISH request fails and the
57      * SIP code is 404 NOT FOUND.
58      */
59     private static final int DEVICE_STATE_PROVISION_ERROR = 2;
60 
61     /**
62      * When the network response SIP code is 489 and the carrier config also indicates that needs
63      * to handle the SIP code 489, the device will be in the BAD EVENT state.
64      */
65     private static final int DEVICE_STATE_BAD_EVENT = 3;
66 
67     /**
68      * The device will be in the NO_RETRY error state when the PUBLISH request fails and the
69      * SIP code is 413 REQUEST ENTITY TOO LARGE.
70      */
71     private static final int DEVICE_STATE_NO_RETRY = 4;
72 
73     @IntDef(value = {
74             DEVICE_STATE_OK,
75             DEVICE_STATE_FORBIDDEN,
76             DEVICE_STATE_PROVISION_ERROR,
77             DEVICE_STATE_BAD_EVENT,
78             DEVICE_STATE_NO_RETRY,
79     }, prefix="DEVICE_STATE_")
80     @Retention(RetentionPolicy.SOURCE)
81     public @interface DeviceStateType {}
82 
83     private static final Map<Integer, String> DEVICE_STATE_DESCRIPTION = new HashMap<>();
84     static {
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_OK, "DEVICE_STATE_OK")85         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_OK, "DEVICE_STATE_OK");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_FORBIDDEN, "DEVICE_STATE_FORBIDDEN")86         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_FORBIDDEN, "DEVICE_STATE_FORBIDDEN");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_PROVISION_ERROR, "DEVICE_STATE_PROVISION_ERROR")87         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_PROVISION_ERROR, "DEVICE_STATE_PROVISION_ERROR");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_BAD_EVENT, "DEVICE_STATE_BAD_EVENT")88         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_BAD_EVENT, "DEVICE_STATE_BAD_EVENT");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_NO_RETRY, "DEVICE_STATE_NO_RETRY")89         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_NO_RETRY, "DEVICE_STATE_NO_RETRY");
90     }
91 
92     /**
93      * The result of the current device state.
94      */
95     public static class DeviceStateResult {
96         final @DeviceStateType int mDeviceState;
97         final @ErrorCode Optional<Integer> mErrorCode;
98         final Optional<Instant> mRequestRetryTime;
99         final Optional<Instant> mExitStateTime;
100 
DeviceStateResult(int deviceState, Optional<Integer> errorCode, Optional<Instant> requestRetryTime, Optional<Instant> exitStateTime)101         public DeviceStateResult(int deviceState, Optional<Integer> errorCode,
102                 Optional<Instant> requestRetryTime, Optional<Instant> exitStateTime) {
103             mDeviceState = deviceState;
104             mErrorCode = errorCode;
105             mRequestRetryTime = requestRetryTime;
106             mExitStateTime = exitStateTime;
107         }
108 
109         /**
110          * Check current state to see if the UCE request is allowed to be executed.
111          */
isRequestForbidden()112         public boolean isRequestForbidden() {
113             switch(mDeviceState) {
114                 case DEVICE_STATE_FORBIDDEN:
115                 case DEVICE_STATE_PROVISION_ERROR:
116                 case DEVICE_STATE_BAD_EVENT:
117                     return true;
118                 default:
119                     return false;
120             }
121         }
122 
123         /**
124          * Check current state to see if only the PUBLISH request is allowed to be executed.
125          */
isPublishRequestBlocked()126         public boolean isPublishRequestBlocked() {
127             switch(mDeviceState) {
128                 case DEVICE_STATE_NO_RETRY:
129                     return true;
130                 default:
131                     return false;
132             }
133         }
134 
getDeviceState()135         public int getDeviceState() {
136             return mDeviceState;
137         }
138 
getErrorCode()139         public Optional<Integer> getErrorCode() {
140             return mErrorCode;
141         }
142 
getRequestRetryTime()143         public Optional<Instant> getRequestRetryTime() {
144             return mRequestRetryTime;
145         }
146 
getRequestRetryAfterMillis()147         public long getRequestRetryAfterMillis() {
148             if (!mRequestRetryTime.isPresent()) {
149                 return 0L;
150             }
151             long retryAfter = ChronoUnit.MILLIS.between(Instant.now(), mRequestRetryTime.get());
152             return (retryAfter < 0L) ? 0L : retryAfter;
153         }
154 
getExitStateTime()155         public Optional<Instant> getExitStateTime() {
156             return mExitStateTime;
157         }
158 
159         /**
160          * Check if the given DeviceStateResult is equal to current DeviceStateResult instance.
161          */
isDeviceStateEqual(DeviceStateResult otherDeviceState)162         public boolean isDeviceStateEqual(DeviceStateResult otherDeviceState) {
163             if ((mDeviceState == otherDeviceState.getDeviceState()) &&
164                     mErrorCode.equals(otherDeviceState.getErrorCode()) &&
165                     mRequestRetryTime.equals(otherDeviceState.getRequestRetryTime()) &&
166                     mExitStateTime.equals(otherDeviceState.getExitStateTime())) {
167                 return true;
168             }
169             return false;
170         }
171 
172         @Override
toString()173         public String toString() {
174             StringBuilder builder = new StringBuilder();
175             builder.append("DeviceState=").append(DEVICE_STATE_DESCRIPTION.get(getDeviceState()))
176                     .append(", ErrorCode=").append(getErrorCode())
177                     .append(", RetryTime=").append(getRequestRetryTime())
178                     .append(", retryAfterMillis=").append(getRequestRetryAfterMillis())
179                     .append(", ExitStateTime=").append(getExitStateTime());
180             return builder.toString();
181         }
182     }
183 
184     private final int mSubId;
185     private final Context mContext;
186     private final UceControllerCallback mUceCtrlCallback;
187 
188     private @DeviceStateType int mDeviceState;
189     private @ErrorCode Optional<Integer> mErrorCode;
190     private Optional<Instant> mRequestRetryTime;
191     private Optional<Instant> mExitStateTime;
192 
UceDeviceState(int subId, Context context, UceControllerCallback uceCtrlCallback)193     public UceDeviceState(int subId, Context context, UceControllerCallback uceCtrlCallback) {
194         mSubId = subId;
195         mContext = context;
196         mUceCtrlCallback = uceCtrlCallback;
197 
198         // Try to restore the device state from the shared preference.
199         boolean restoreFromPref = false;
200         Optional<DeviceStateResult> deviceState = UceUtils.restoreDeviceState(mContext, mSubId);
201         if (deviceState.isPresent()) {
202             restoreFromPref = true;
203             mDeviceState = deviceState.get().getDeviceState();
204             mErrorCode = deviceState.get().getErrorCode();
205             mRequestRetryTime = deviceState.get().getRequestRetryTime();
206             mExitStateTime = deviceState.get().getExitStateTime();
207         } else {
208             mDeviceState = DEVICE_STATE_OK;
209             mErrorCode = Optional.empty();
210             mRequestRetryTime = Optional.empty();
211             mExitStateTime = Optional.empty();
212         }
213         logd("UceDeviceState: restore from sharedPref=" + restoreFromPref + ", " +
214                 getCurrentState());
215     }
216 
217     /**
218      * Check and setup the timer to exit the request disallowed state. This method is called when
219      * the DeviceState has been initialized completed and need to restore the timer.
220      */
checkSendResetDeviceStateTimer()221     public synchronized void checkSendResetDeviceStateTimer() {
222         logd("checkSendResetDeviceStateTimer: time=" + mExitStateTime);
223         if (!mExitStateTime.isPresent()) {
224             return;
225         }
226         long expirySec = ChronoUnit.SECONDS.between(Instant.now(), mExitStateTime.get());
227         if (expirySec < 0) {
228             expirySec = 0;
229         }
230         // Setup timer to exit the request disallowed state.
231         mUceCtrlCallback.setupResetDeviceStateTimer(expirySec);
232     }
233 
234     /**
235      * @return The current device state.
236      */
getCurrentState()237     public synchronized DeviceStateResult getCurrentState() {
238         return new DeviceStateResult(mDeviceState, mErrorCode, mRequestRetryTime, mExitStateTime);
239     }
240 
241     /**
242      * Update the device state to determine whether the device is allowed to send requests or not.
243      *  @param sipCode The SIP CODE of the request result.
244      *  @param reason The reason from the network response.
245      *  @param requestType The type of the request.
246      */
refreshDeviceState(int sipCode, String reason, @RequestType int requestType)247     public synchronized void refreshDeviceState(int sipCode, String reason,
248             @RequestType int requestType) {
249         logd("refreshDeviceState: sipCode=" + sipCode + ", reason=" + reason +
250                 ", requestResponseType=" + UceController.REQUEST_TYPE_DESCRIPTION.get(requestType));
251 
252         // Get the current device status before updating the state.
253         DeviceStateResult previousState = getCurrentState();
254 
255         // Update the device state based on the given sip code.
256         switch (sipCode) {
257             case NetworkSipCode.SIP_CODE_FORBIDDEN:   // sip 403
258             case NetworkSipCode.SIP_CODE_SERVER_TIMEOUT: // sip 504
259                 if (requestType == UceController.REQUEST_TYPE_PUBLISH) {
260                     // Provisioning error for publish request.
261                     setDeviceState(DEVICE_STATE_PROVISION_ERROR);
262                     updateErrorCode(sipCode, reason, requestType);
263                     // There is no request retry time for SIP code 403
264                     removeRequestRetryTime();
265                     // No timer to exit the forbidden state.
266                     removeExitStateTimer();
267                 }
268                 break;
269 
270             case NetworkSipCode.SIP_CODE_NOT_FOUND:  // sip 404
271                 // DeviceState only handles 404 NOT FOUND error for PUBLISH request.
272                 if (requestType == UceController.REQUEST_TYPE_PUBLISH) {
273                     setDeviceState(DEVICE_STATE_PROVISION_ERROR);
274                     updateErrorCode(sipCode, reason, requestType);
275                     // There is no request retry time for SIP code 404
276                     removeRequestRetryTime();
277                     // No timer to exit this state.
278                     removeExitStateTimer();
279                 }
280                 break;
281 
282             case NetworkSipCode.SIP_CODE_BAD_EVENT:   // sip 489
283                 if (UceUtils.isRequestForbiddenBySip489(mContext, mSubId)) {
284                     setDeviceState(DEVICE_STATE_BAD_EVENT);
285                     updateErrorCode(sipCode, reason, requestType);
286                     // Setup the request retry time.
287                     setupRequestRetryTime();
288                     // Setup the timer to exit the BAD EVENT state.
289                     setupExitStateTimer();
290                 }
291                 break;
292 
293             case NetworkSipCode.SIP_CODE_OK:
294             case NetworkSipCode.SIP_CODE_ACCEPTED:
295                 // Reset the device state when the network response is OK.
296                 resetInternal();
297                 break;
298 
299             case NetworkSipCode.SIP_CODE_REQUEST_ENTITY_TOO_LARGE:   // sip 413
300             case NetworkSipCode.SIP_CODE_TEMPORARILY_UNAVAILABLE:   // sip 480
301             case NetworkSipCode.SIP_CODE_BUSY:   // sip 486
302             case NetworkSipCode.SIP_CODE_SERVER_INTERNAL_ERROR:   // sip 500
303             case NetworkSipCode.SIP_CODE_SERVICE_UNAVAILABLE:   // sip 503
304             case NetworkSipCode.SIP_CODE_BUSY_EVERYWHERE:   // sip 600
305             case NetworkSipCode.SIP_CODE_DECLINE:   // sip 603
306                 if (requestType == UceController.REQUEST_TYPE_PUBLISH) {
307                     setDeviceState(DEVICE_STATE_NO_RETRY);
308                     // There is no request retry time for SIP code 413
309                     removeRequestRetryTime();
310                     // No timer to exit this state.
311                     removeExitStateTimer();
312                 }
313                 break;
314         }
315 
316         // Get the updated device state.
317         DeviceStateResult currentState = getCurrentState();
318 
319         // Remove the device state from the shared preference if the device is allowed to execute
320         // UCE requests. Otherwise, save the new state into the shared preference when the device
321         // state has changed.
322         if (!currentState.isRequestForbidden()) {
323             removeDeviceStateFromPreference();
324         } else if (!currentState.isDeviceStateEqual(previousState)) {
325             saveDeviceStateToPreference(currentState);
326         }
327 
328         logd("refreshDeviceState: previous: " + previousState + ", current: " + currentState);
329     }
330 
331     /**
332      * Reset the device state. This method is called when the ImsService triggers to send the
333      * PUBLISH request.
334      */
resetDeviceState()335     public synchronized void resetDeviceState() {
336         DeviceStateResult previousState = getCurrentState();
337         resetInternal();
338         DeviceStateResult currentState = getCurrentState();
339 
340         // Remove the device state from shared preference because the device state has been reset.
341         removeDeviceStateFromPreference();
342 
343         logd("resetDeviceState: previous=" + previousState + ", current=" + currentState);
344     }
345 
346     /**
347      * The internal method to reset the device state. This method doesn't
348      */
resetInternal()349     private void resetInternal() {
350         setDeviceState(DEVICE_STATE_OK);
351         resetErrorCode();
352         removeRequestRetryTime();
353         removeExitStateTimer();
354     }
355 
setDeviceState(@eviceStateType int latestState)356     private void setDeviceState(@DeviceStateType int latestState) {
357         if (mDeviceState != latestState) {
358             mDeviceState = latestState;
359         }
360     }
361 
updateErrorCode(int sipCode, String reason, @RequestType int requestType)362     private void updateErrorCode(int sipCode, String reason, @RequestType int requestType) {
363         Optional<Integer> newErrorCode = Optional.of(NetworkSipCode.getCapabilityErrorFromSipCode(
364                     sipCode, reason, requestType));
365         if (!mErrorCode.equals(newErrorCode)) {
366             mErrorCode = newErrorCode;
367         }
368     }
369 
resetErrorCode()370     private void resetErrorCode() {
371         if (mErrorCode.isPresent()) {
372             mErrorCode = Optional.empty();
373         }
374     }
375 
setupRequestRetryTime()376     private void setupRequestRetryTime() {
377         /*
378          * Update the request retry time when A) it has not been assigned yet or B) it has past the
379          * current time and need to be re-assigned a new retry time.
380          */
381         if (!mRequestRetryTime.isPresent() || mRequestRetryTime.get().isAfter(Instant.now())) {
382             long retryInterval = UceUtils.getRequestRetryInterval(mContext, mSubId);
383             mRequestRetryTime = Optional.of(Instant.now().plusMillis(retryInterval));
384         }
385     }
386 
removeRequestRetryTime()387     private void removeRequestRetryTime() {
388         if (mRequestRetryTime.isPresent()) {
389             mRequestRetryTime = Optional.empty();
390         }
391     }
392 
393     /**
394      * Set the timer to exit the device disallowed state and then trigger a PUBLISH request.
395      */
setupExitStateTimer()396     private void setupExitStateTimer() {
397         if (!mExitStateTime.isPresent()) {
398             long expirySec = UceUtils.getNonRcsCapabilitiesCacheExpiration(mContext, mSubId);
399             mExitStateTime = Optional.of(Instant.now().plusSeconds(expirySec));
400             logd("setupExitStateTimer: expirationSec=" + expirySec + ", time=" + mExitStateTime);
401 
402             // Setup timer to exit the request disallowed state.
403             mUceCtrlCallback.setupResetDeviceStateTimer(expirySec);
404         }
405     }
406 
407     /**
408      * Remove the exit state timer.
409      */
removeExitStateTimer()410     private void removeExitStateTimer() {
411         if (mExitStateTime.isPresent()) {
412             mExitStateTime = Optional.empty();
413             mUceCtrlCallback.clearResetDeviceStateTimer();
414         }
415     }
416 
417     /**
418      * Save the given device sate to the shared preference.
419      * @param deviceState
420      */
saveDeviceStateToPreference(DeviceStateResult deviceState)421     private void saveDeviceStateToPreference(DeviceStateResult deviceState) {
422         boolean result = UceUtils.saveDeviceStateToPreference(mContext, mSubId, deviceState);
423         logd("saveDeviceStateToPreference: result=" + result + ", state= " + deviceState);
424     }
425 
426     /**
427      * Remove the device state information from the shared preference because the device is allowed
428      * execute UCE requests.
429      */
removeDeviceStateFromPreference()430     private void removeDeviceStateFromPreference() {
431         boolean result = UceUtils.removeDeviceStateFromPreference(mContext, mSubId);
432         logd("removeDeviceStateFromPreference: result=" + result);
433     }
434 
logd(String log)435     private void logd(String log) {
436         Log.d(LOG_TAG, getLogPrefix().append(log).toString());
437     }
438 
getLogPrefix()439     private StringBuilder getLogPrefix() {
440         StringBuilder builder = new StringBuilder("[");
441         builder.append(mSubId);
442         builder.append("] ");
443         return builder;
444     }
445 }
446