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.request; 18 19 import android.telephony.ims.RcsUceAdapter; 20 import android.telephony.ims.RcsUceAdapter.ErrorCode; 21 import android.text.TextUtils; 22 import android.util.Log; 23 24 import com.android.ims.rcs.uce.util.UceUtils; 25 26 import java.util.Optional; 27 28 /** 29 * The helper class to analyze the result of the callback onTerminated to determine whether the 30 * subscription request should be retried or not. 31 */ 32 public class SubscriptionTerminatedHelper { 33 34 private static final String LOG_TAG = UceUtils.getLogPrefix() + "SubscriptionTerminated"; 35 36 // The terminated reasons defined in RFC 3265 3.2.4 37 private static final String REASON_DEACTIVATED = "deactivated"; 38 private static final String REASON_PROBATION = "probation"; 39 private static final String REASON_REJECTED = "rejected"; 40 private static final String REASON_TIMEOUT = "timeout"; 41 private static final String REASON_GIVEUP = "giveup"; 42 private static final String REASON_NORESOURCE = "noresource"; 43 44 /** 45 * The analysis result of the callback onTerminated. 46 */ 47 static class TerminatedResult { 48 private final @ErrorCode Optional<Integer> mErrorCode; 49 private final long mRetryAfterMillis; 50 TerminatedResult(@rrorCode Optional<Integer> errorCode, long retryAfterMillis)51 public TerminatedResult(@ErrorCode Optional<Integer> errorCode, long retryAfterMillis) { 52 mErrorCode = errorCode; 53 mRetryAfterMillis = retryAfterMillis; 54 } 55 56 /** 57 * @return the error code when the request is failed. Optional.empty if the request is 58 * successful. 59 */ getErrorCode()60 public Optional<Integer> getErrorCode() { 61 return mErrorCode; 62 } 63 getRetryAfterMillis()64 public long getRetryAfterMillis() { 65 return mRetryAfterMillis; 66 } 67 toString()68 public String toString() { 69 StringBuilder builder = new StringBuilder(); 70 builder.append("TerminatedResult: ") 71 .append("errorCode=").append(mErrorCode) 72 .append(", retryAfterMillis=").append(mRetryAfterMillis); 73 return builder.toString(); 74 } 75 } 76 77 /** 78 * According to the RFC 3265, Check the given reason to see whether clients should retry the 79 * subscribe request. 80 * <p> 81 * See RFC 3265 3.2.4 for the detail. 82 * 83 * @param reason The reason why the subscribe request is terminated. The reason is given by the 84 * network and it could be empty. 85 * @param retryAfterMillis How long should clients wait before retrying. 86 * @param allCapsHaveReceived Whether all the request contact capabilities have been received. 87 */ getAnalysisResult(String reason, long retryAfterMillis, boolean allCapsHaveReceived)88 public static TerminatedResult getAnalysisResult(String reason, long retryAfterMillis, 89 boolean allCapsHaveReceived) { 90 TerminatedResult result = null; 91 if (TextUtils.isEmpty(reason)) { 92 /* 93 * When the value of retryAfterMillis is larger then zero, the client should retry. 94 */ 95 if (retryAfterMillis > 0L) { 96 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), 97 retryAfterMillis); 98 } 99 } else if (REASON_DEACTIVATED.equalsIgnoreCase(reason)) { 100 /* 101 * When the reason is "deactivated", clients should retry immediately. 102 */ 103 long retry = getRequestRetryAfterMillis(retryAfterMillis); 104 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry); 105 } else if (REASON_PROBATION.equalsIgnoreCase(reason)) { 106 /* 107 * When the reason is "probation", it means that the subscription has been terminated, 108 * but the client should retry at some later time. 109 */ 110 long retry = getRequestRetryAfterMillis(retryAfterMillis); 111 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry); 112 } else if (REASON_REJECTED.equalsIgnoreCase(reason)) { 113 /* 114 * When the reason is "rejected", it means that the subscription has been terminated 115 * due to chang in authorization policy. Clients should NOT retry. 116 */ 117 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_AUTHORIZED), 0L); 118 } else if (REASON_TIMEOUT.equalsIgnoreCase(reason)) { 119 if (retryAfterMillis > 0L) { 120 /* 121 * When the parameter "retryAfterMillis" is greater than zero, it means that the 122 * ImsService requires clients should retry later. 123 */ 124 long retry = getRequestRetryAfterMillis(retryAfterMillis); 125 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_REQUEST_TIMEOUT), 126 retry); 127 } else if (!allCapsHaveReceived) { 128 /* 129 * The ImsService does not require to retry when the parameter "retryAfterMillis" 130 * is zero. However, the request is still failed because it has not received all 131 * the capabilities updated from the network. 132 */ 133 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_REQUEST_TIMEOUT), 0L); 134 } else { 135 /* 136 * The subscribe request is successfully when the parameter retryAfter is zero and 137 * all the request capabilities have been received. 138 */ 139 result = new TerminatedResult(Optional.empty(), 0L); 140 } 141 } else if (REASON_GIVEUP.equalsIgnoreCase(reason)) { 142 /* 143 * The subscription has been terminated because the notifier could no obtain 144 * authorization in a timely fashion. Clients could retry the subscribe request. 145 */ 146 long retry = getRequestRetryAfterMillis(retryAfterMillis); 147 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_AUTHORIZED), retry); 148 } else if (REASON_NORESOURCE.equalsIgnoreCase(reason)) { 149 /* 150 * The subscription has been terminated because the resource is no longer exists. 151 * Clients should NOT retry. 152 */ 153 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_NOT_FOUND), 0L); 154 } else if (retryAfterMillis > 0L) { 155 /* 156 * Even if the reason is not listed above, clients should retry the request as long as 157 * the value of retry is non-zero. 158 */ 159 long retry = getRequestRetryAfterMillis(retryAfterMillis); 160 result = new TerminatedResult(Optional.of(RcsUceAdapter.ERROR_GENERIC_FAILURE), retry); 161 } 162 163 // The request should be successful. when the terminated is not in the above cases 164 if (result == null) { 165 result = new TerminatedResult(Optional.empty(), 0L); 166 } 167 168 Log.d(LOG_TAG, "getAnalysisResult: reason=" + reason + ", retry=" + retryAfterMillis + 169 ", allCapsHaveReceived=" + allCapsHaveReceived + ", " + result); 170 return result; 171 } 172 173 /* 174 * Get the appropriated retryAfterMillis for the subscribe request. 175 */ getRequestRetryAfterMillis(long retryAfterMillis)176 private static long getRequestRetryAfterMillis(long retryAfterMillis) { 177 // Return the minimum retry after millis if the given retryAfterMillis is less than the 178 // minimum value. 179 long minRetryAfterMillis = UceUtils.getMinimumRequestRetryAfterMillis(); 180 return (retryAfterMillis < minRetryAfterMillis) ? minRetryAfterMillis : retryAfterMillis; 181 } 182 } 183