Loading src/java/com/android/ims/rcs/uce/request/UceRequestManager.java +85 −3 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; Loading @@ -59,6 +60,14 @@ public class UceRequestManager { private static final String LOG_TAG = UceUtils.getLogPrefix() + "UceRequestManager"; /** * When enabled, skip the request queue for requests that have numbers with valid cached * capabilities and return that cached info directly. * Note: This also has a CTS test associated with it, so this can not be disabled without * disabling the corresponding RcsUceAdapterTest#testCacheQuerySuccessWhenNetworkBlocked test. */ private static final boolean FEATURE_SHORTCUT_QUEUE_FOR_CACHED_CAPS = true; /** * Testing interface used to mock UceUtils in testing. */ Loading Loading @@ -487,12 +496,27 @@ public class UceRequestManager { private void sendRequestInternal(@UceRequestType int type, List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) throws RemoteException { UceRequestCoordinator requestCoordinator = null; List<Uri> nonCachedUris = uriList; if (FEATURE_SHORTCUT_QUEUE_FOR_CACHED_CAPS && !skipFromCache) { nonCachedUris = sendCachedCapInfoToRequester(type, uriList, callback); if (uriList.size() != nonCachedUris.size()) { logd("sendRequestInternal: shortcut queue for caps - request reduced from " + uriList.size() + " entries to " + nonCachedUris.size() + " entries"); } else { logd("sendRequestInternal: shortcut queue for caps - no cached caps."); } if (nonCachedUris.isEmpty()) { logd("sendRequestInternal: shortcut complete, sending success result"); callback.onComplete(); return; } } if (sUceUtilsProxy.isPresenceCapExchangeEnabled(mContext, mSubId) && sUceUtilsProxy.isPresenceSupported(mContext, mSubId)) { requestCoordinator = createSubscribeRequestCoordinator(type, uriList, skipFromCache, callback); requestCoordinator = createSubscribeRequestCoordinator(type, nonCachedUris, skipFromCache, callback); } else if (sUceUtilsProxy.isSipOptionsSupported(mContext, mSubId)) { requestCoordinator = createOptionsRequestCoordinator(type, uriList, callback); requestCoordinator = createOptionsRequestCoordinator(type, nonCachedUris, callback); } if (requestCoordinator == null) { Loading @@ -513,6 +537,64 @@ public class UceRequestManager { addRequestCoordinator(requestCoordinator); } /** * Try to get the valid capabilities associated with the URI List specified from the EAB cache. * If one or more of the numbers from the URI List have valid cached capabilities, return them * to the requester now and remove them from the returned List of URIs that will require a * network query. * @param type The type of query * @param uriList The List of URIs that we want to send cached capabilities for * @param callback The callback used to communicate with the remote requester * @return The List of URIs that were not found in the capability cache and will require a * network query. */ private List<Uri> sendCachedCapInfoToRequester(int type, List<Uri> uriList, IRcsUceControllerCallback callback) { List<Uri> nonCachedUris = new ArrayList<>(uriList); List<RcsContactUceCapability> numbersWithCachedCaps = getCapabilitiesFromCache(type, nonCachedUris); try { if (!numbersWithCachedCaps.isEmpty()) { logd("sendCachedCapInfoToRequester: cached caps found for " + numbersWithCachedCaps.size() + " entries. Notifying requester."); // Notify caller of the numbers that have cached caps callback.onCapabilitiesReceived(numbersWithCachedCaps); } } catch (RemoteException e) { logw("sendCachedCapInfoToRequester, error sending cap info back to requester: " + e); } // remove these numbers from the numbers pending a cap query from the network. for (RcsContactUceCapability c : numbersWithCachedCaps) { nonCachedUris.removeIf(uri -> c.getContactUri().equals(uri)); } return nonCachedUris; } /** * Get the capabilities for the List of given URIs * @param requestType The request type, used to determine if the cached info is "fresh" enough. * @param uriList The List of URIs that we will be requesting cached capabilities for. * @return A list of capabilities corresponding to the subset of numbers that still have * valid cache data associated with them. */ private List<RcsContactUceCapability> getCapabilitiesFromCache(int requestType, List<Uri> uriList) { List<EabCapabilityResult> resultList = Collections.emptyList(); if (requestType == UceRequest.REQUEST_TYPE_CAPABILITY) { resultList = mRequestMgrCallback.getCapabilitiesFromCache(uriList); } else if (requestType == UceRequest.REQUEST_TYPE_AVAILABILITY) { // Always get the first element if the request type is availability. resultList = Collections.singletonList( mRequestMgrCallback.getAvailabilityFromCache(uriList.get(0))); } // Map from EabCapabilityResult -> RcsContactUceCapability. // Pull out only items that have valid cache data. return resultList.stream().filter(Objects::nonNull) .filter(result -> result.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL) .map(EabCapabilityResult::getContactCapabilities) .filter(Objects::nonNull).collect(Collectors.toList()); } private UceRequestCoordinator createSubscribeRequestCoordinator(final @UceRequestType int type, final List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) { SubscribeRequestCoordinator.Builder builder; Loading tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java +114 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.ims.rcs.uce.request; import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE; import static android.telephony.ims.RcsContactUceCapability.SOURCE_TYPE_CACHED; import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE; import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_CAPABILITY_UPDATE; Loading Loading @@ -49,12 +50,15 @@ import androidx.test.filters.SmallTest; import com.android.ims.ImsTestBase; import com.android.ims.rcs.uce.UceController; import com.android.ims.rcs.uce.UceController.UceControllerCallback; import com.android.ims.rcs.uce.eab.EabCapabilityResult; import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback; import com.android.ims.rcs.uce.request.UceRequestManager.UceUtilsProxy; import com.android.ims.rcs.uce.util.FeatureTags; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; Loading Loading @@ -132,6 +136,116 @@ public class UceRequestManagerTest extends ImsTestBase { verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L); } /** * Test cache hit shortcut, where we return valid cache results when they exist before adding * the request to the queue. */ @Test @SmallTest public void testCacheHitShortcut() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); uriList.add(Uri.fromParts("sip", "test", null)); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = uriList.stream().map(uri -> new EabCapabilityResult(uri, EabCapabilityResult.EAB_QUERY_SUCCESSFUL, new RcsContactUceCapability.PresenceBuilder(uri, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())) .collect(Collectors.toList()); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback).onCapabilitiesReceived( cachedNumbers.stream().map(EabCapabilityResult::getContactCapabilities).collect( Collectors.toList())); verify(mCapabilitiesCallback).onComplete(); // The cache should have been hit, so no network requests should have been generated. verify(mRequestRepository, never()).addRequestCoordinator(any()); } /** * Test cache hit shortcut, but in this case the cache result was expired. This should generate * a network request. */ @Test @SmallTest public void testCacheExpiredShortcut() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); uriList.add(Uri.fromParts("sip", "test", null)); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = uriList.stream().map(uri -> new EabCapabilityResult(uri, EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE, new RcsContactUceCapability.PresenceBuilder(uri, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())) .collect(Collectors.toList()); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback, never()).onCapabilitiesReceived(any()); verify(mCapabilitiesCallback, never()).onComplete(); // A network request should have been generated for the expired contact. verify(mRequestRepository).addRequestCoordinator(any()); } /** * Test cache hit shortcut, where we return valid cache results when they exist before adding * the request to the queue. This case also tests the case where one entry of requested caps is * in the cache and the other isn't. We should receive a response for cached caps immediately * and generate a network request for the one that isnt. */ @Test @SmallTest public void testCacheHitShortcutForSubsetOfCaps() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); Uri uri1 = Uri.fromParts("sip", "cachetest", null); uriList.add(uri1); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = new ArrayList<>(); EabCapabilityResult cachedItem = new EabCapabilityResult(uri1, EabCapabilityResult.EAB_QUERY_SUCCESSFUL, new RcsContactUceCapability.PresenceBuilder(uri1, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build()); cachedNumbers.add(cachedItem); // Add an entry that is not part of cache Uri uri2 = Uri.fromParts("sip", "nettest", null); uriList.add(uri2); cachedNumbers.add(new EabCapabilityResult(uri2, EabCapabilityResult.EAB_CONTACT_NOT_FOUND_FAILURE, new RcsContactUceCapability.PresenceBuilder(uri2, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback).onCapabilitiesReceived( Collections.singletonList(cachedItem.getContactCapabilities())); verify(mCapabilitiesCallback, never()).onComplete(); // The cache should have been hit, but there was also entry that was not in the cache, so // ensure that is requested. verify(mRequestRepository).addRequestCoordinator(any()); } @Test @SmallTest public void testRequestManagerCallback() throws Exception { Loading Loading
src/java/com/android/ims/rcs/uce/request/UceRequestManager.java +85 −3 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; Loading @@ -59,6 +60,14 @@ public class UceRequestManager { private static final String LOG_TAG = UceUtils.getLogPrefix() + "UceRequestManager"; /** * When enabled, skip the request queue for requests that have numbers with valid cached * capabilities and return that cached info directly. * Note: This also has a CTS test associated with it, so this can not be disabled without * disabling the corresponding RcsUceAdapterTest#testCacheQuerySuccessWhenNetworkBlocked test. */ private static final boolean FEATURE_SHORTCUT_QUEUE_FOR_CACHED_CAPS = true; /** * Testing interface used to mock UceUtils in testing. */ Loading Loading @@ -487,12 +496,27 @@ public class UceRequestManager { private void sendRequestInternal(@UceRequestType int type, List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) throws RemoteException { UceRequestCoordinator requestCoordinator = null; List<Uri> nonCachedUris = uriList; if (FEATURE_SHORTCUT_QUEUE_FOR_CACHED_CAPS && !skipFromCache) { nonCachedUris = sendCachedCapInfoToRequester(type, uriList, callback); if (uriList.size() != nonCachedUris.size()) { logd("sendRequestInternal: shortcut queue for caps - request reduced from " + uriList.size() + " entries to " + nonCachedUris.size() + " entries"); } else { logd("sendRequestInternal: shortcut queue for caps - no cached caps."); } if (nonCachedUris.isEmpty()) { logd("sendRequestInternal: shortcut complete, sending success result"); callback.onComplete(); return; } } if (sUceUtilsProxy.isPresenceCapExchangeEnabled(mContext, mSubId) && sUceUtilsProxy.isPresenceSupported(mContext, mSubId)) { requestCoordinator = createSubscribeRequestCoordinator(type, uriList, skipFromCache, callback); requestCoordinator = createSubscribeRequestCoordinator(type, nonCachedUris, skipFromCache, callback); } else if (sUceUtilsProxy.isSipOptionsSupported(mContext, mSubId)) { requestCoordinator = createOptionsRequestCoordinator(type, uriList, callback); requestCoordinator = createOptionsRequestCoordinator(type, nonCachedUris, callback); } if (requestCoordinator == null) { Loading @@ -513,6 +537,64 @@ public class UceRequestManager { addRequestCoordinator(requestCoordinator); } /** * Try to get the valid capabilities associated with the URI List specified from the EAB cache. * If one or more of the numbers from the URI List have valid cached capabilities, return them * to the requester now and remove them from the returned List of URIs that will require a * network query. * @param type The type of query * @param uriList The List of URIs that we want to send cached capabilities for * @param callback The callback used to communicate with the remote requester * @return The List of URIs that were not found in the capability cache and will require a * network query. */ private List<Uri> sendCachedCapInfoToRequester(int type, List<Uri> uriList, IRcsUceControllerCallback callback) { List<Uri> nonCachedUris = new ArrayList<>(uriList); List<RcsContactUceCapability> numbersWithCachedCaps = getCapabilitiesFromCache(type, nonCachedUris); try { if (!numbersWithCachedCaps.isEmpty()) { logd("sendCachedCapInfoToRequester: cached caps found for " + numbersWithCachedCaps.size() + " entries. Notifying requester."); // Notify caller of the numbers that have cached caps callback.onCapabilitiesReceived(numbersWithCachedCaps); } } catch (RemoteException e) { logw("sendCachedCapInfoToRequester, error sending cap info back to requester: " + e); } // remove these numbers from the numbers pending a cap query from the network. for (RcsContactUceCapability c : numbersWithCachedCaps) { nonCachedUris.removeIf(uri -> c.getContactUri().equals(uri)); } return nonCachedUris; } /** * Get the capabilities for the List of given URIs * @param requestType The request type, used to determine if the cached info is "fresh" enough. * @param uriList The List of URIs that we will be requesting cached capabilities for. * @return A list of capabilities corresponding to the subset of numbers that still have * valid cache data associated with them. */ private List<RcsContactUceCapability> getCapabilitiesFromCache(int requestType, List<Uri> uriList) { List<EabCapabilityResult> resultList = Collections.emptyList(); if (requestType == UceRequest.REQUEST_TYPE_CAPABILITY) { resultList = mRequestMgrCallback.getCapabilitiesFromCache(uriList); } else if (requestType == UceRequest.REQUEST_TYPE_AVAILABILITY) { // Always get the first element if the request type is availability. resultList = Collections.singletonList( mRequestMgrCallback.getAvailabilityFromCache(uriList.get(0))); } // Map from EabCapabilityResult -> RcsContactUceCapability. // Pull out only items that have valid cache data. return resultList.stream().filter(Objects::nonNull) .filter(result -> result.getStatus() == EabCapabilityResult.EAB_QUERY_SUCCESSFUL) .map(EabCapabilityResult::getContactCapabilities) .filter(Objects::nonNull).collect(Collectors.toList()); } private UceRequestCoordinator createSubscribeRequestCoordinator(final @UceRequestType int type, final List<Uri> uriList, boolean skipFromCache, IRcsUceControllerCallback callback) { SubscribeRequestCoordinator.Builder builder; Loading
tests/src/com/android/ims/rcs/uce/request/UceRequestManagerTest.java +114 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.ims.rcs.uce.request; import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE; import static android.telephony.ims.RcsContactUceCapability.SOURCE_TYPE_CACHED; import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_CACHED_CAPABILITY_UPDATE; import static com.android.ims.rcs.uce.request.UceRequestCoordinator.REQUEST_UPDATE_CAPABILITY_UPDATE; Loading Loading @@ -49,12 +50,15 @@ import androidx.test.filters.SmallTest; import com.android.ims.ImsTestBase; import com.android.ims.rcs.uce.UceController; import com.android.ims.rcs.uce.UceController.UceControllerCallback; import com.android.ims.rcs.uce.eab.EabCapabilityResult; import com.android.ims.rcs.uce.request.UceRequestManager.RequestManagerCallback; import com.android.ims.rcs.uce.request.UceRequestManager.UceUtilsProxy; import com.android.ims.rcs.uce.util.FeatureTags; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.junit.After; import org.junit.Before; Loading Loading @@ -132,6 +136,116 @@ public class UceRequestManagerTest extends ImsTestBase { verify(mCapabilitiesCallback).onError(RcsUceAdapter.ERROR_GENERIC_FAILURE, 0L); } /** * Test cache hit shortcut, where we return valid cache results when they exist before adding * the request to the queue. */ @Test @SmallTest public void testCacheHitShortcut() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); uriList.add(Uri.fromParts("sip", "test", null)); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = uriList.stream().map(uri -> new EabCapabilityResult(uri, EabCapabilityResult.EAB_QUERY_SUCCESSFUL, new RcsContactUceCapability.PresenceBuilder(uri, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())) .collect(Collectors.toList()); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback).onCapabilitiesReceived( cachedNumbers.stream().map(EabCapabilityResult::getContactCapabilities).collect( Collectors.toList())); verify(mCapabilitiesCallback).onComplete(); // The cache should have been hit, so no network requests should have been generated. verify(mRequestRepository, never()).addRequestCoordinator(any()); } /** * Test cache hit shortcut, but in this case the cache result was expired. This should generate * a network request. */ @Test @SmallTest public void testCacheExpiredShortcut() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); uriList.add(Uri.fromParts("sip", "test", null)); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = uriList.stream().map(uri -> new EabCapabilityResult(uri, EabCapabilityResult.EAB_CONTACT_EXPIRED_FAILURE, new RcsContactUceCapability.PresenceBuilder(uri, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())) .collect(Collectors.toList()); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback, never()).onCapabilitiesReceived(any()); verify(mCapabilitiesCallback, never()).onComplete(); // A network request should have been generated for the expired contact. verify(mRequestRepository).addRequestCoordinator(any()); } /** * Test cache hit shortcut, where we return valid cache results when they exist before adding * the request to the queue. This case also tests the case where one entry of requested caps is * in the cache and the other isn't. We should receive a response for cached caps immediately * and generate a network request for the one that isnt. */ @Test @SmallTest public void testCacheHitShortcutForSubsetOfCaps() throws Exception { UceRequestManager requestManager = getUceRequestManager(); requestManager.setsUceUtilsProxy(getUceUtilsProxy(true, true, true, false, true, 10)); Handler handler = requestManager.getUceRequestHandler(); List<Uri> uriList = new ArrayList<>(); Uri uri1 = Uri.fromParts("sip", "cachetest", null); uriList.add(uri1); // Simulate a cache entry for each item in the uriList List<EabCapabilityResult> cachedNumbers = new ArrayList<>(); EabCapabilityResult cachedItem = new EabCapabilityResult(uri1, EabCapabilityResult.EAB_QUERY_SUCCESSFUL, new RcsContactUceCapability.PresenceBuilder(uri1, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build()); cachedNumbers.add(cachedItem); // Add an entry that is not part of cache Uri uri2 = Uri.fromParts("sip", "nettest", null); uriList.add(uri2); cachedNumbers.add(new EabCapabilityResult(uri2, EabCapabilityResult.EAB_CONTACT_NOT_FOUND_FAILURE, new RcsContactUceCapability.PresenceBuilder(uri2, CAPABILITY_MECHANISM_PRESENCE, SOURCE_TYPE_CACHED).build())); doReturn(cachedNumbers).when(mCallback).getCapabilitiesFromCache(uriList); requestManager.sendCapabilityRequest(uriList, false, mCapabilitiesCallback); waitForHandlerAction(handler, 500L); // Extract caps from EabCapabilityResult and ensure the Lists match. verify(mCapabilitiesCallback).onCapabilitiesReceived( Collections.singletonList(cachedItem.getContactCapabilities())); verify(mCapabilitiesCallback, never()).onComplete(); // The cache should have been hit, but there was also entry that was not in the cache, so // ensure that is requested. verify(mRequestRepository).addRequestCoordinator(any()); } @Test @SmallTest public void testRequestManagerCallback() throws Exception { Loading