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, R)78         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_OK, "DEVICE_STATE_OK");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_FORBIDDEN, R)79         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_FORBIDDEN, "DEVICE_STATE_FORBIDDEN");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_PROVISION_ERROR, R)80         DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_PROVISION_ERROR, "DEVICE_STATE_PROVISION_ERROR");
DEVICE_STATE_DESCRIPTION.put(DEVICE_STATE_BAD_EVENT, R)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                     updateErrorCode(sipCode, reason, requestType);
242                     // There is no request retry time for SIP code 403
243                     removeRequestRetryTime();
244                     // No timer to exit the forbidden state.
245                     removeExitStateTimer();
246                 }
247                 break;
248 
249             case NetworkSipCode.SIP_CODE_NOT_FOUND:  // sip 404
250                 // DeviceState only handles 404 NOT FOUND error for PUBLISH request.
251                 if (requestType == UceController.REQUEST_TYPE_PUBLISH) {
252                     setDeviceState(DEVICE_STATE_PROVISION_ERROR);
253                     updateErrorCode(sipCode, reason, requestType);
254                     // There is no request retry time for SIP code 404
255                     removeRequestRetryTime();
256                     // No timer to exit this state.
257                     removeExitStateTimer();
258                 }
259                 break;
260 
261             case NetworkSipCode.SIP_CODE_BAD_EVENT:   // sip 489
262                 if (UceUtils.isRequestForbiddenBySip489(mContext, mSubId)) {
263                     setDeviceState(DEVICE_STATE_BAD_EVENT);
264                     updateErrorCode(sipCode, reason, requestType);
265                     // Setup the request retry time.
266                     setupRequestRetryTime();
267                     // Setup the timer to exit the BAD EVENT state.
268                     setupExitStateTimer();
269                 }
270                 break;
271 
272             case NetworkSipCode.SIP_CODE_OK:
273             case NetworkSipCode.SIP_CODE_ACCEPTED:
274                 // Reset the device state when the network response is OK.
275                 resetInternal();
276                 break;
277         }
278 
279         // Get the updated device state.
280         DeviceStateResult currentState = getCurrentState();
281 
282         // Remove the device state from the shared preference if the device is allowed to execute
283         // UCE requests. Otherwise, save the new state into the shared preference when the device
284         // state has changed.
285         if (!currentState.isRequestForbidden()) {
286             removeDeviceStateFromPreference();
287         } else if (!currentState.isDeviceStateEqual(previousState)) {
288             saveDeviceStateToPreference(currentState);
289         }
290 
291         logd("refreshDeviceState: previous: " + previousState + ", current: " + currentState);
292     }
293 
294     /**
295      * Reset the device state. This method is called when the ImsService triggers to send the
296      * PUBLISH request.
297      */
resetDeviceState()298     public synchronized void resetDeviceState() {
299         DeviceStateResult previousState = getCurrentState();
300         resetInternal();
301         DeviceStateResult currentState = getCurrentState();
302 
303         // Remove the device state from shared preference because the device state has been reset.
304         removeDeviceStateFromPreference();
305 
306         logd("resetDeviceState: previous=" + previousState + ", current=" + currentState);
307     }
308 
309     /**
310      * The internal method to reset the device state. This method doesn't
311      */
resetInternal()312     private void resetInternal() {
313         setDeviceState(DEVICE_STATE_OK);
314         resetErrorCode();
315         removeRequestRetryTime();
316         removeExitStateTimer();
317     }
318 
setDeviceState(@eviceStateType int latestState)319     private void setDeviceState(@DeviceStateType int latestState) {
320         if (mDeviceState != latestState) {
321             mDeviceState = latestState;
322         }
323     }
324 
updateErrorCode(int sipCode, String reason, @RequestType int requestType)325     private void updateErrorCode(int sipCode, String reason, @RequestType int requestType) {
326         Optional<Integer> newErrorCode = Optional.of(NetworkSipCode.getCapabilityErrorFromSipCode(
327                     sipCode, reason, requestType));
328         if (!mErrorCode.equals(newErrorCode)) {
329             mErrorCode = newErrorCode;
330         }
331     }
332 
resetErrorCode()333     private void resetErrorCode() {
334         if (mErrorCode.isPresent()) {
335             mErrorCode = Optional.empty();
336         }
337     }
338 
setupRequestRetryTime()339     private void setupRequestRetryTime() {
340         /*
341          * Update the request retry time when A) it has not been assigned yet or B) it has past the
342          * current time and need to be re-assigned a new retry time.
343          */
344         if (!mRequestRetryTime.isPresent() || mRequestRetryTime.get().isAfter(Instant.now())) {
345             long retryInterval = UceUtils.getRequestRetryInterval(mContext, mSubId);
346             mRequestRetryTime = Optional.of(Instant.now().plusMillis(retryInterval));
347         }
348     }
349 
removeRequestRetryTime()350     private void removeRequestRetryTime() {
351         if (mRequestRetryTime.isPresent()) {
352             mRequestRetryTime = Optional.empty();
353         }
354     }
355 
356     /**
357      * Set the timer to exit the device disallowed state and then trigger a PUBLISH request.
358      */
setupExitStateTimer()359     private void setupExitStateTimer() {
360         if (!mExitStateTime.isPresent()) {
361             long expirySec = UceUtils.getNonRcsCapabilitiesCacheExpiration(mContext, mSubId);
362             mExitStateTime = Optional.of(Instant.now().plusSeconds(expirySec));
363             logd("setupExitStateTimer: expirationSec=" + expirySec + ", time=" + mExitStateTime);
364 
365             // Setup timer to exit the request disallowed state.
366             mUceCtrlCallback.setupResetDeviceStateTimer(expirySec);
367         }
368     }
369 
370     /**
371      * Remove the exit state timer.
372      */
removeExitStateTimer()373     private void removeExitStateTimer() {
374         if (mExitStateTime.isPresent()) {
375             mExitStateTime = Optional.empty();
376             mUceCtrlCallback.clearResetDeviceStateTimer();
377         }
378     }
379 
380     /**
381      * Save the given device sate to the shared preference.
382      * @param deviceState
383      */
saveDeviceStateToPreference(DeviceStateResult deviceState)384     private void saveDeviceStateToPreference(DeviceStateResult deviceState) {
385         boolean result = UceUtils.saveDeviceStateToPreference(mContext, mSubId, deviceState);
386         logd("saveDeviceStateToPreference: result=" + result + ", state= " + deviceState);
387     }
388 
389     /**
390      * Remove the device state information from the shared preference because the device is allowed
391      * execute UCE requests.
392      */
removeDeviceStateFromPreference()393     private void removeDeviceStateFromPreference() {
394         boolean result = UceUtils.removeDeviceStateFromPreference(mContext, mSubId);
395         logd("removeDeviceStateFromPreference: result=" + result);
396     }
397 
logd(String log)398     private void logd(String log) {
399         Log.d(LOG_TAG, getLogPrefix().append(log).toString());
400     }
401 
getLogPrefix()402     private StringBuilder getLogPrefix() {
403         StringBuilder builder = new StringBuilder("[");
404         builder.append(mSubId);
405         builder.append("] ");
406         return builder;
407     }
408 }
409