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