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