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.net.Uri; 20 import android.telephony.ims.RcsUceAdapter; 21 import android.telephony.ims.RcsContactUceCapability; 22 import android.util.Log; 23 24 import com.android.ims.rcs.uce.UceDeviceState.DeviceStateResult; 25 import com.android.ims.rcs.uce.eab.EabCapabilityResult; 26 import com.android.ims.rcs.uce.presence.pidfparser.PidfParserUtils; 27 import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback; 28 import com.android.ims.rcs.uce.util.UceUtils; 29 import com.android.internal.annotations.VisibleForTesting; 30 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.stream.Collectors; 36 37 /** 38 * The base class of the UCE request to request the capabilities from the carrier network. 39 */ 40 public abstract class CapabilityRequest implements UceRequest { 41 42 private static final String LOG_TAG = UceUtils.getLogPrefix() + "CapabilityRequest"; 43 44 protected final int mSubId; 45 protected final long mTaskId; 46 protected final List<Uri> mUriList; 47 protected final @UceRequestType int mRequestType; 48 protected final RequestManagerCallback mRequestManagerCallback; 49 protected final CapabilityRequestResponse mRequestResponse; 50 51 protected volatile long mCoordinatorId; 52 protected volatile boolean mIsFinished; 53 protected volatile boolean mSkipGettingFromCache; 54 protected int mCurrentRetryCount; 55 protected boolean mRetryEnabled; 56 CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback)57 public CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback) { 58 mSubId = subId; 59 mRequestType = type; 60 mUriList = new ArrayList<>(); 61 mRequestManagerCallback = callback; 62 mRequestResponse = new CapabilityRequestResponse(); 63 mTaskId = UceUtils.generateTaskId(); 64 mCurrentRetryCount = 0; 65 mRetryEnabled = false; 66 } 67 68 @VisibleForTesting CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback, CapabilityRequestResponse requestResponse)69 public CapabilityRequest(int subId, @UceRequestType int type, RequestManagerCallback callback, 70 CapabilityRequestResponse requestResponse) { 71 mSubId = subId; 72 mRequestType = type; 73 mUriList = new ArrayList<>(); 74 mRequestManagerCallback = callback; 75 mRequestResponse = requestResponse; 76 mTaskId = UceUtils.generateTaskId(); 77 mCurrentRetryCount = 0; 78 mRetryEnabled = false; 79 } 80 81 @Override setRequestCoordinatorId(long coordinatorId)82 public void setRequestCoordinatorId(long coordinatorId) { 83 mCoordinatorId = coordinatorId; 84 } 85 86 @Override getRequestCoordinatorId()87 public long getRequestCoordinatorId() { 88 return mCoordinatorId; 89 } 90 91 @Override getTaskId()92 public long getTaskId() { 93 return mTaskId; 94 } 95 96 @Override onFinish()97 public void onFinish() { 98 mIsFinished = true; 99 // Remove the timeout timer of this request 100 mRequestManagerCallback.removeRequestTimeoutTimer(mTaskId); 101 } 102 103 @Override setContactUri(List<Uri> uris)104 public void setContactUri(List<Uri> uris) { 105 mUriList.addAll(uris); 106 mRequestResponse.setRequestContacts(uris); 107 } 108 getContactUri()109 public List<Uri> getContactUri() { 110 return Collections.unmodifiableList(mUriList); 111 } 112 113 /** 114 * Set to check if this request should be getting the capabilities from the cache. The flag is 115 * set when the request is triggered by the capability polling service. The contacts from the 116 * capability polling service are already expired, skip checking from the cache. 117 */ setSkipGettingFromCache(boolean skipFromCache)118 public void setSkipGettingFromCache(boolean skipFromCache) { 119 mSkipGettingFromCache = skipFromCache; 120 } 121 122 /** 123 * Return if the capabilities request should skip getting from the cache. The flag is set when 124 * the request is triggered by the capability polling service and the request doesn't need to 125 * check the cache again. 126 */ isSkipGettingFromCache()127 private boolean isSkipGettingFromCache() { 128 return mSkipGettingFromCache; 129 } 130 131 /** 132 * @return The RequestResponse instance associated with this request. 133 */ getRequestResponse()134 public CapabilityRequestResponse getRequestResponse() { 135 return mRequestResponse; 136 } 137 138 /** 139 * Start executing this request. 140 */ 141 @Override executeRequest()142 public void executeRequest() { 143 // Return if this request is not allowed to be executed. 144 if (!isRequestAllowed()) { 145 logd("executeRequest: The request is not allowed."); 146 mRequestManagerCallback.notifyRequestError(mCoordinatorId, mTaskId); 147 return; 148 } 149 150 // Get the contact capabilities from the cache including the expired capabilities. 151 final List<EabCapabilityResult> eabResultList = getCapabilitiesFromCache(); 152 153 // Get all the unexpired capabilities from the EAB result list and add to the response. 154 final List<RcsContactUceCapability> cachedCapList = isSkipGettingFromCache() ? 155 Collections.EMPTY_LIST : getUnexpiredCapabilities(eabResultList); 156 mRequestResponse.addCachedCapabilities(cachedCapList); 157 158 logd("executeRequest: cached capabilities size=" + cachedCapList.size()); 159 160 // Get the rest contacts which are not in the cache or has expired. 161 final List<Uri> expiredUris = getRequestingFromNetworkUris(cachedCapList); 162 163 // For those uris that are not in the cache or have expired, we should request their 164 // capabilities from the network. However, we still need to check whether these contacts 165 // are in the throttling list. If the contact is in the throttling list, even if it has 166 // expired, we will get the cached capabilities. 167 final List<RcsContactUceCapability> throttlingUris = 168 getFromThrottlingList(expiredUris, eabResultList); 169 mRequestResponse.addCachedCapabilities(throttlingUris); 170 171 logd("executeRequest: contacts in throttling list size=" + throttlingUris.size()); 172 173 // Notify that the cached capabilities are updated. 174 if (!cachedCapList.isEmpty() || !throttlingUris.isEmpty()) { 175 mRequestManagerCallback.notifyCachedCapabilitiesUpdated(mCoordinatorId, mTaskId); 176 } 177 178 // Get the rest contacts which need to request capabilities from the network. 179 List<Uri> requestCapUris = getRequestingFromNetworkUris(cachedCapList, throttlingUris); 180 181 logd("executeRequest: requestCapUris size=" + requestCapUris.size()); 182 183 // Notify that it doesn't need to request capabilities from the network when all the 184 // requested capabilities can be retrieved from cache. Otherwise, it needs to request 185 // capabilities from the network for those contacts which cannot retrieve capabilities from 186 // the cache. 187 if (requestCapUris.isEmpty()) { 188 mRequestManagerCallback.notifyNoNeedRequestFromNetwork(mCoordinatorId, mTaskId); 189 } else { 190 requestCapabilities(requestCapUris); 191 } 192 } 193 getRetryCount()194 public int getRetryCount() { 195 return mCurrentRetryCount; 196 } 197 setRetryCount(int retryCount)198 public void setRetryCount(int retryCount) { 199 mCurrentRetryCount = retryCount; 200 } 201 isRetryEnabled()202 public boolean isRetryEnabled() { 203 return mRetryEnabled; 204 } 205 setRetryEnabled(boolean enabled)206 public void setRetryEnabled(boolean enabled) { 207 mRetryEnabled = enabled; 208 } 209 210 // Check whether this request is allowed to be executed or not. isRequestAllowed()211 private boolean isRequestAllowed() { 212 if (mUriList == null || mUriList.isEmpty()) { 213 logw("isRequestAllowed: uri is empty"); 214 mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE); 215 return false; 216 } 217 218 if (mIsFinished) { 219 logw("isRequestAllowed: This request is finished"); 220 mRequestResponse.setRequestInternalError(RcsUceAdapter.ERROR_GENERIC_FAILURE); 221 return false; 222 } 223 224 DeviceStateResult deviceStateResult = mRequestManagerCallback.getDeviceState(); 225 if (deviceStateResult.isRequestForbidden()) { 226 logw("isRequestAllowed: The device is disallowed."); 227 mRequestResponse.setRequestInternalError( 228 deviceStateResult.getErrorCode().orElse(RcsUceAdapter.ERROR_GENERIC_FAILURE)); 229 return false; 230 } 231 return true; 232 } 233 234 // Get the cached capabilities by the given request type. getCapabilitiesFromCache()235 private List<EabCapabilityResult> getCapabilitiesFromCache() { 236 List<EabCapabilityResult> resultList = null; 237 if (mRequestType == REQUEST_TYPE_CAPABILITY) { 238 resultList = mRequestManagerCallback.getCapabilitiesFromCacheIncludingExpired(mUriList); 239 } else if (mRequestType == REQUEST_TYPE_AVAILABILITY) { 240 // Always get the first element if the request type is availability. 241 Uri uri = mUriList.get(0); 242 EabCapabilityResult eabResult = 243 mRequestManagerCallback.getAvailabilityFromCacheIncludingExpired(uri); 244 resultList = new ArrayList<>(); 245 resultList.add(eabResult); 246 } 247 if (resultList == null) { 248 return Collections.emptyList(); 249 } 250 return resultList; 251 } 252 253 /** 254 * Get the unexpired contact capabilities from the given EAB result list. 255 * @param list the query result from the EAB 256 */ getUnexpiredCapabilities(List<EabCapabilityResult> list)257 private List<RcsContactUceCapability> getUnexpiredCapabilities(List<EabCapabilityResult> list) { 258 return list.stream() 259 .filter(Objects::nonNull) 260 .filter(result -> result.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL) 261 .map(EabCapabilityResult::getContactCapabilities) 262 .filter(Objects::nonNull) 263 .collect(Collectors.toList()); 264 } 265 266 /** 267 * Get the contact uris which cannot retrieve capabilities from the cache. 268 * @param cachedCapList The capabilities which are already stored in the cache. 269 */ getRequestingFromNetworkUris(List<RcsContactUceCapability> cachedCapList)270 private List<Uri> getRequestingFromNetworkUris(List<RcsContactUceCapability> cachedCapList) { 271 return mUriList.stream() 272 .filter(uri -> cachedCapList.stream() 273 .noneMatch(cap -> cap.getContactUri().equals(uri))) 274 .collect(Collectors.toList()); 275 } 276 277 /** 278 * Get the contact uris which cannot retrieve capabilities from the cache. 279 * @param cachedCapList The capabilities which are already stored in the cache. 280 * @param throttlingUris The capabilities which are in the throttling list. 281 */ getRequestingFromNetworkUris(List<RcsContactUceCapability> cachedCapList, List<RcsContactUceCapability> throttlingUris)282 private List<Uri> getRequestingFromNetworkUris(List<RcsContactUceCapability> cachedCapList, 283 List<RcsContactUceCapability> throttlingUris) { 284 // We won't request the network query for those contacts in the cache and in the 285 // throttling list. Merging the two list and get the rest contact uris. 286 List<RcsContactUceCapability> notNetworkQueryList = new ArrayList<>(cachedCapList); 287 notNetworkQueryList.addAll(throttlingUris); 288 return getRequestingFromNetworkUris(notNetworkQueryList); 289 } 290 291 /** 292 * Get the contact capabilities for those uri are in the throttling list. If the contact uri is 293 * in the throttling list, the capabilities will be retrieved from cache even if it has expired. 294 * If the capabilities cannot be found, return the non-RCS contact capabilities instead. 295 * @param expiredUris the expired/unknown uris to check whether are in the throttling list 296 * @return the contact capabilities for the uris are in the throttling list 297 */ getFromThrottlingList(final List<Uri> expiredUris, final List<EabCapabilityResult> eabResultList)298 private List<RcsContactUceCapability> getFromThrottlingList(final List<Uri> expiredUris, 299 final List<EabCapabilityResult> eabResultList) { 300 List<RcsContactUceCapability> resultList = new ArrayList<>(); 301 List<RcsContactUceCapability> notFoundFromCacheList = new ArrayList<>(); 302 303 // Retrieve the uris put in the throttling list from the expired/unknown contacts. 304 List<Uri> throttlingUris = mRequestManagerCallback.getInThrottlingListUris(expiredUris); 305 306 // For these uris in the throttling list, check whether their capabilities are in the cache. 307 List<EabCapabilityResult> throttlingUriFoundInEab = new ArrayList<>(); 308 for (Uri uri : throttlingUris) { 309 for (EabCapabilityResult eabResult : eabResultList) { 310 if (eabResult.getContact().equals(uri)) { 311 throttlingUriFoundInEab.add(eabResult); 312 break; 313 } 314 } 315 } 316 317 throttlingUriFoundInEab.forEach(eabResult -> { 318 if (eabResult.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL || 319 eabResult.getStatus() == EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE) { 320 // The capabilities are found, add to the result list 321 resultList.add(eabResult.getContactCapabilities()); 322 } else { 323 // Cannot get the capabilities from cache, create the non-RCS capabilities instead. 324 notFoundFromCacheList.add(PidfParserUtils.getNotFoundContactCapabilities( 325 eabResult.getContact())); 326 } 327 }); 328 329 if (!notFoundFromCacheList.isEmpty()) { 330 resultList.addAll(notFoundFromCacheList); 331 } 332 333 logd("getFromThrottlingList: requesting uris in the list size=" + throttlingUris.size() + 334 ", generate non-RCS size=" + notFoundFromCacheList.size()); 335 return resultList; 336 } 337 338 /** 339 * Set the timeout timer of this request. 340 */ setupRequestTimeoutTimer()341 protected void setupRequestTimeoutTimer() { 342 long timeoutAfterMs = UceUtils.getCapRequestTimeoutAfterMillis(); 343 logd("setupRequestTimeoutTimer(ms): " + timeoutAfterMs); 344 mRequestManagerCallback.setRequestTimeoutTimer(mCoordinatorId, mTaskId, timeoutAfterMs); 345 } 346 347 /* 348 * Requests capabilities from IMS. The inherited request is required to override this method 349 * to define the behavior of requesting capabilities. 350 */ requestCapabilities(List<Uri> requestCapUris)351 protected abstract void requestCapabilities(List<Uri> requestCapUris); 352 logd(String log)353 protected void logd(String log) { 354 Log.d(LOG_TAG, getLogPrefix().append(log).toString()); 355 } 356 logw(String log)357 protected void logw(String log) { 358 Log.w(LOG_TAG, getLogPrefix().append(log).toString()); 359 } 360 logi(String log)361 protected void logi(String log) { 362 Log.i(LOG_TAG, getLogPrefix().append(log).toString()); 363 } 364 getLogPrefix()365 private StringBuilder getLogPrefix() { 366 StringBuilder builder = new StringBuilder("["); 367 builder.append(mSubId).append("][taskId=").append(mTaskId).append("] "); 368 return builder; 369 } 370 } 371