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