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