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