diff options
author | Peter Qiu <zqiu@google.com> | 2016-12-19 13:12:13 -0800 |
---|---|---|
committer | Peter Qiu <zqiu@google.com> | 2017-01-07 19:06:28 -0800 |
commit | 33c46cd7132df4ce72eee0ed2783e1a1e15bc007 (patch) | |
tree | 5a40249488635e92ec337d4d4345e7b65829d2f8 | |
parent | 87c6f1b149804685e46c18d2ad11262f611c9255 (diff) |
hotspot2: add ANQPRequestManager for managing ANQP requests
The main objective is to hold off ANQP requests to an AP
after the previous request is unanwered or failed.
This can eaisly be expanded in the future to throttle ANQP requests
further by taking into consideration of the current connection
status and other criteria, to reduce the impact of ANQP
requests on the network performance and the power consumption.
Bug: 33746564
Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Change-Id: I906508f72832a9e461be6b2db2ceacb997765bb8
5 files changed, 613 insertions, 17 deletions
diff --git a/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java new file mode 100644 index 000000000..fd756c3a2 --- /dev/null +++ b/service/java/com/android/server/wifi/hotspot2/ANQPRequestManager.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.hotspot2; + +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.wifi.Clock; +import com.android.server.wifi.ScanDetail; +import com.android.server.wifi.hotspot2.anqp.Constants; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class for managing sending of ANQP requests. This manager will ignore ANQP requests for a + * period of time (hold off time) to a specified AP if the previous request to that AP goes + * unanswered or failed. The hold off time will increase exponentially until the max is reached. + */ +public class ANQPRequestManager { + private static final String TAG = "ANQPRequestManager"; + + private final PasspointEventHandler mPasspointHandler; + private final Clock mClock; + + /** + * List of pending ANQP request associated with an AP (BSSID). + */ + private final Map<Long, ScanDetail> mPendingQueries; + + /** + * List of hold off time information associated with APs specified by their BSSID. + * Used to determine when an ANQP request can be send to the corresponding AP after the + * previous request goes unanswered or failed. + */ + private final Map<Long, HoldOffInfo> mHoldOffInfo; + + /** + * Minimum number of milliseconds to wait for before attempting ANQP queries to the same AP + * after previous request goes unanswered or failed. + */ + @VisibleForTesting + public static final int BASE_HOLDOFF_TIME_MILLISECONDS = 10000; + + /** + * Max value for the hold off counter for unanswered/failed queries. This limits the maximum + * hold off time to: + * BASE_HOLDOFF_TIME_MILLISECONDS * 2^MAX_HOLDOFF_COUNT + * which is 640 seconds. + */ + @VisibleForTesting + public static final int MAX_HOLDOFF_COUNT = 6; + + private static final List<Constants.ANQPElementType> R1_ANQP_BASE_SET = Arrays.asList( + Constants.ANQPElementType.ANQPVenueName, + Constants.ANQPElementType.ANQPIPAddrAvailability, + Constants.ANQPElementType.ANQPNAIRealm, + Constants.ANQPElementType.ANQP3GPPNetwork, + Constants.ANQPElementType.ANQPDomName); + + private static final List<Constants.ANQPElementType> R2_ANQP_BASE_SET = Arrays.asList( + Constants.ANQPElementType.HSFriendlyName, + Constants.ANQPElementType.HSWANMetrics, + Constants.ANQPElementType.HSConnCapability); + + /** + * Class to keep track of AP status for ANQP requests. + */ + private class HoldOffInfo { + /** + * Current hold off count. Will max out at {@link #MAX_HOLDOFF_COUNT}. + */ + public int holdOffCount; + /** + * The time stamp in milliseconds when we're allow to send ANQP request to the + * corresponding AP. + */ + public long holdOffExpirationTime; + } + + public ANQPRequestManager(PasspointEventHandler handler, Clock clock) { + mPasspointHandler = handler; + mClock = clock; + mPendingQueries = new HashMap<>(); + mHoldOffInfo = new HashMap<>(); + } + + /** + * Request ANQP elements from the specified AP. This will request the basic Release 1 ANQP + * elements {@link #R1_ANQP_BASE_SET}. Additional elements will be requested based on the + * information provided in the Information Element (Roaming Consortium OI count and the + * supported Hotspot 2.0 release version). + * + * @param bssid The BSSID of the AP + * @param scanDetail The ScanDetail associated with this request + * @param rcOIs Flag indicating the inclusion of roaming consortium OIs. When set to true, + * Roaming Consortium ANQP element will be requested + * @param hsReleaseR2 Flag indicating the support of Hotspot 2.0 Release 2. When set to true, + * the Release 2 ANQP elements {@link #R2_ANQP_BASE_SET} will be requested + * @return true if a request was sent successfully + */ + public boolean requestANQPElements(long bssid, ScanDetail scanDetail, boolean rcOIs, + boolean hsReleaseR2) { + // Check if we are allow to send the request now. + if (!canSendRequestNow(bssid)) { + return false; + } + + // No need to hold off future requests for send failures. + if (!mPasspointHandler.requestANQP(bssid, getRequestElementIDs(rcOIs, hsReleaseR2))) { + return false; + } + + // Update hold off info on when we are allowed to send the next ANQP request to + // the given AP. + updateHoldOffInfo(bssid); + + mPendingQueries.put(bssid, scanDetail); + return true; + } + + /** + * Notification of the completion of an ANQP request. + * + * @param bssid The BSSID of the AP + * @param success Flag indicating the result of the query + * @return {@link ScanDetail} associated with the completed request + */ + public ScanDetail onRequestCompleted(long bssid, boolean success) { + if (success) { + // Query succeeded. No need to hold off request to the given AP. + mHoldOffInfo.remove(bssid); + } + return mPendingQueries.remove(bssid); + } + + /** + * Check if we are allowed to send ANQP request to the specified AP now. + * + * @param bssid The BSSID of an AP + * @return true if we are allowed to send the request now + */ + private boolean canSendRequestNow(long bssid) { + long currentTime = mClock.getElapsedSinceBootMillis(); + HoldOffInfo info = mHoldOffInfo.get(bssid); + if (info != null && info.holdOffExpirationTime > currentTime) { + Log.d(TAG, "Not allowed to send ANQP request to " + bssid + " for another " + + (info.holdOffExpirationTime - currentTime) / 1000 + " seconds"); + return false; + } + + return true; + } + + /** + * Update the ANQP request hold off info associated with the given AP. + * + * @param bssid The BSSID of an AP + */ + private void updateHoldOffInfo(long bssid) { + HoldOffInfo info = mHoldOffInfo.get(bssid); + if (info == null) { + info = new HoldOffInfo(); + mHoldOffInfo.put(bssid, info); + } + info.holdOffExpirationTime = mClock.getElapsedSinceBootMillis() + + BASE_HOLDOFF_TIME_MILLISECONDS * (1 << info.holdOffCount); + if (info.holdOffCount < MAX_HOLDOFF_COUNT) { + info.holdOffCount++; + } + } + + /** + * Get the list of ANQP element IDs to request based on the Hotspot 2.0 release number + * and the ANQP OI count indicated in the Information Element. + * + * @param rcOIs Flag indicating the inclusion of roaming consortium OIs + * @param hsReleaseR2 Flag indicating support of Hotspot 2.0 Release 2 + * @return List of ANQP Element ID + */ + private static List<Constants.ANQPElementType> getRequestElementIDs(boolean rcOIs, + boolean hsReleaseR2) { + List<Constants.ANQPElementType> requestList = new ArrayList<>(); + requestList.addAll(R1_ANQP_BASE_SET); + if (rcOIs) { + requestList.add(Constants.ANQPElementType.ANQPRoamingConsortium); + } + + if (hsReleaseR2) { + requestList.addAll(R2_ANQP_BASE_SET); + } + return requestList; + } +} diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java index 88b04a379..da991061e 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java @@ -73,7 +73,7 @@ public class PasspointManager { private final PasspointObjectFactory mObjectFactory; private final Map<String, PasspointProvider> mProviders; private final AnqpCache mAnqpCache; - private final Map<Long, ANQPNetworkKey> mPendingAnqpQueries; + private final ANQPRequestManager mAnqpRequestManager; // Counter used for assigning unique identifier to a provider. private long mProviderID; @@ -87,22 +87,24 @@ public class PasspointManager { @Override public void onANQPResponse(long bssid, Map<Constants.ANQPElementType, ANQPElement> anqpElements) { - // Remove the entry from pending list. - ANQPNetworkKey anqpKey = mPendingAnqpQueries.remove(bssid); - - if (anqpElements == null) { - // Query failed. - // TODO(b/33246489): keep track of failed ANQP queries for backing off - // future queries. - return; - } - - if (anqpKey == null) { + // Notify request manager for the completion of a request. + ScanDetail scanDetail = + mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null); + if (anqpElements == null || scanDetail == null) { + // Query failed or the request wasn't originated from us (not tracked by the + // request manager). Nothing to be done. return; } // Add new entry to the cache. + NetworkDetail networkDetail = scanDetail.getNetworkDetail(); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(networkDetail.getSSID(), + networkDetail.getBSSID(), networkDetail.getHESSID(), + networkDetail.getAnqpDomainID()); mAnqpCache.addEntry(anqpKey, anqpElements); + + // Update ANQP elements in the ScanDetail. + scanDetail.propagateANQPInfo(anqpElements); } @Override @@ -149,7 +151,7 @@ public class PasspointManager { mObjectFactory = objectFactory; mProviders = new HashMap<>(); mAnqpCache = objectFactory.makeAnqpCache(clock); - mPendingAnqpQueries = new HashMap<>(); + mAnqpRequestManager = objectFactory.makeANQPRequestManager(mHandler, clock); mProviderID = 0; // TODO(zqiu): load providers from the persistent storage. } @@ -260,10 +262,9 @@ public class PasspointManager { ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey); if (anqpEntry == null) { - if (!mPendingAnqpQueries.containsValue(anqpKey)) { - // TODO(b/33246489): Request ANQP data. - mPendingAnqpQueries.put(networkDetail.getBSSID(), anqpKey); - } + mAnqpRequestManager.requestANQPElements(networkDetail.getBSSID(), scanDetail, + networkDetail.getAnqpOICount() > 0, + networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2); return new ArrayList<Pair<PasspointProvider, PasspointMatch>>(); } diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java index 69bf99a9e..139bf3285 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java @@ -62,4 +62,15 @@ public class PasspointObjectFactory{ public AnqpCache makeAnqpCache(Clock clock) { return new AnqpCache(clock); } + + /** + * Create an instance of {@link ANQPRequestManager}. + * + * @param handler Instance of {@link PasspointEventHandler} + * @param clock Instance of {@link Clock} + * @return {@link ANQPRequestManager} + */ + public ANQPRequestManager makeANQPRequestManager(PasspointEventHandler handler, Clock clock) { + return new ANQPRequestManager(handler, clock); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java new file mode 100644 index 000000000..08b37deb9 --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/ANQPRequestManagerTest.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi.hotspot2; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyObject; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.wifi.Clock; +import com.android.server.wifi.ScanDetail; +import com.android.server.wifi.hotspot2.anqp.Constants; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.List; + +/** + * Unit tests for {@link com.android.server.wifi.hotspot2.ANQPRequestManager}. + */ +@SmallTest +public class ANQPRequestManagerTest { + private static final long TEST_BSSID = 0x123456L; + private static final ScanDetail TEST_SCAN_DETAIL = mock(ScanDetail.class); + + private static final List<Constants.ANQPElementType> R1_ANQP_WITHOUT_RC = Arrays.asList( + Constants.ANQPElementType.ANQPVenueName, + Constants.ANQPElementType.ANQPIPAddrAvailability, + Constants.ANQPElementType.ANQPNAIRealm, + Constants.ANQPElementType.ANQP3GPPNetwork, + Constants.ANQPElementType.ANQPDomName); + + private static final List<Constants.ANQPElementType> R1_ANQP_WITH_RC = Arrays.asList( + Constants.ANQPElementType.ANQPVenueName, + Constants.ANQPElementType.ANQPIPAddrAvailability, + Constants.ANQPElementType.ANQPNAIRealm, + Constants.ANQPElementType.ANQP3GPPNetwork, + Constants.ANQPElementType.ANQPDomName, + Constants.ANQPElementType.ANQPRoamingConsortium); + + private static final List<Constants.ANQPElementType> R1R2_ANQP_WITHOUT_RC = Arrays.asList( + Constants.ANQPElementType.ANQPVenueName, + Constants.ANQPElementType.ANQPIPAddrAvailability, + Constants.ANQPElementType.ANQPNAIRealm, + Constants.ANQPElementType.ANQP3GPPNetwork, + Constants.ANQPElementType.ANQPDomName, + Constants.ANQPElementType.HSFriendlyName, + Constants.ANQPElementType.HSWANMetrics, + Constants.ANQPElementType.HSConnCapability); + + private static final List<Constants.ANQPElementType> R1R2_ANQP_WITH_RC = Arrays.asList( + Constants.ANQPElementType.ANQPVenueName, + Constants.ANQPElementType.ANQPIPAddrAvailability, + Constants.ANQPElementType.ANQPNAIRealm, + Constants.ANQPElementType.ANQP3GPPNetwork, + Constants.ANQPElementType.ANQPDomName, + Constants.ANQPElementType.ANQPRoamingConsortium, + Constants.ANQPElementType.HSFriendlyName, + Constants.ANQPElementType.HSWANMetrics, + Constants.ANQPElementType.HSConnCapability); + + @Mock PasspointEventHandler mHandler; + @Mock Clock mClock; + ANQPRequestManager mManager; + + /** + * Test setup. + */ + @Before + public void setUp() throws Exception { + initMocks(this); + mManager = new ANQPRequestManager(mHandler, mClock); + } + + /** + * Verify that the expected set of ANQP elements are being requested when the targeted AP + * doesn't provide roaming consortium OIs and doesn't support Hotspot 2.0 Release 2 ANQP + * elements, based on the IEs in the scan result . + * + * @throws Exception + */ + @Test + public void requestR1ANQPElementsWithoutRC() throws Exception { + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + } + + /** + * Verify that the expected set of ANQP elements are being requested when the targeted AP + * does provide roaming consortium OIs and doesn't support Hotspot 2.0 Release ANQP elements, + * based on the IEs in the scan result. + * + * @throws Exception + */ + @Test + public void requestR1ANQPElementsWithRC() throws Exception { + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITH_RC)).thenReturn(true); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, true, false)); + } + + /** + * Verify that the expected set of ANQP elements are being requested when the targeted AP + * doesn't provide roaming consortium OIs and does support Hotspot 2.0 Release ANQP elements, + * based on the IEs in the scan result. + * + * @throws Exception + */ + @Test + public void requestR1R2ANQPElementsWithoutRC() throws Exception { + when(mHandler.requestANQP(TEST_BSSID, R1R2_ANQP_WITHOUT_RC)).thenReturn(true); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, true)); + } + + /** + * Verify that the expected set of ANQP elements are being requested when the targeted AP + * does provide roaming consortium OIs and support Hotspot 2.0 Release ANQP elements, + * based on the IEs in the scan result. + * + * @throws Exception + */ + @Test + public void requestR1R2ANQPElementsWithRC() throws Exception { + when(mHandler.requestANQP(TEST_BSSID, R1R2_ANQP_WITH_RC)).thenReturn(true); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, true, true)); + } + + /** + * Verify that attempt to request ANQP elements from an AP will fail when there is a request + * already pending. The request will succeed when the hold off time is up. + * + * @throws Exception + */ + @Test + public void requestANQPElementsWithPendingRequest() throws Exception { + // Send the initial request. + long startTime = 0; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + + // Attempt another request will fail while one is still pending and hold off time is not up + // yet. + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1); + assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + verify(mHandler, never()).requestANQP(anyLong(), anyObject()); + reset(mHandler); + + // Attempt other request will succeed after the hold off time is up. + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()) + .thenReturn(startTime + ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + } + + /** + * Verify that an immediate attempt to request ANQP elements from an AP will succeed when + * the previous request is failed on sending. + * + * @throws Exception + */ + @Test + public void requestANQPElementsAfterRequestSendFailure() throws Exception { + // Initial request failed to send. + long startTime = 0; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(false); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime); + assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + + // Verify that new request is not being held off after previous send failure. + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + } + + /** + * Verify that an immediate attempt to request ANQP elements from an AP will succeed when + * the previous request is completed with success. + * + * @throws Exception + */ + @Test + public void requestANQPElementsAfterRequestSucceeded() throws Exception { + // Send the initial request. + long startTime = 0; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + + // Request completed with success. + mManager.onRequestCompleted(TEST_BSSID, true); + + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + } + + /** + * Verify that an immediate attempt to request ANQP elements from an AP will fail when + * the previous request is completed with failure. The request will succeed after the + * hold off time is up. + * + * @throws Exception + */ + @Test + public void requestANQPElementsAfterRequestFailed() throws Exception { + // Send the initial request. + long startTime = 0; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + + // Request completed with failure. + mManager.onRequestCompleted(TEST_BSSID, false); + + // Attempt another request will fail since the hold off time is not up yet. + when(mClock.getElapsedSinceBootMillis()).thenReturn(startTime + 1); + assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + verify(mHandler, never()).requestANQP(anyLong(), anyObject()); + + // Attempt another request will succeed after the hold off time is up. + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()) + .thenReturn(startTime + ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + } + + /** + * Verify the hold off time for each unanswered query, and that it will stay the same after + * reaching the max hold off count {@link ANQPRequestManager#MAX_HOLDOFF_COUNT}. + * + * @throws Exception + */ + @Test + public void requestANQPElementsWithMaxRetries() throws Exception { + long currentTime = 0; + + // Initial request. + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + + // Sending the request with the hold off time based on the current hold off count. + for (int i = 0; i <= ANQPRequestManager.MAX_HOLDOFF_COUNT; i++) { + long currentHoldOffTime = ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS * (1 << i); + currentTime += (currentHoldOffTime - 1); + + // Request will fail before the hold off time is up. + when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime); + assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + verify(mHandler, never()).requestANQP(anyLong(), anyObject()); + + // Request will succeed when the hold off time is up. + currentTime += 1; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + } + + // Verify that the hold off time is max out at the maximum hold off count. + currentTime += (ANQPRequestManager.BASE_HOLDOFF_TIME_MILLISECONDS + * (1 << ANQPRequestManager.MAX_HOLDOFF_COUNT) - 1); + + when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime); + assertFalse(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + verify(mHandler, never()).requestANQP(anyLong(), anyObject()); + + currentTime += 1; + when(mHandler.requestANQP(TEST_BSSID, R1_ANQP_WITHOUT_RC)).thenReturn(true); + when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTime); + assertTrue(mManager.requestANQPElements(TEST_BSSID, TEST_SCAN_DETAIL, false, false)); + reset(mHandler); + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java index 59dfa3b58..74bb10e84 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java @@ -25,10 +25,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyMap; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; @@ -50,6 +52,9 @@ import com.android.server.wifi.SIMAccessor; import com.android.server.wifi.ScanDetail; import com.android.server.wifi.WifiKeyStore; import com.android.server.wifi.WifiNative; +import com.android.server.wifi.hotspot2.anqp.ANQPElement; +import com.android.server.wifi.hotspot2.anqp.Constants.ANQPElementType; +import com.android.server.wifi.hotspot2.anqp.DomainNameElement; import org.junit.Before; import org.junit.Test; @@ -57,7 +62,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointManager}. @@ -85,6 +93,7 @@ public class PasspointManagerTest { @Mock PasspointObjectFactory mObjectFactory; @Mock PasspointEventHandler.Callbacks mCallbacks; @Mock AnqpCache mAnqpCache; + @Mock ANQPRequestManager mAnqpRequestManager; PasspointManager mManager; /** Sets up test. */ @@ -92,6 +101,8 @@ public class PasspointManagerTest { public void setUp() throws Exception { initMocks(this); when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache); + when(mObjectFactory.makeANQPRequestManager(any(PasspointEventHandler.class), eq(mClock))) + .thenReturn(mAnqpRequestManager); mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock, mSimAccessor, mObjectFactory); ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks = @@ -192,6 +203,61 @@ public class PasspointManagerTest { } /** + * Verify that the ANQP elements will be added to the ANQP cache on receiving a successful + * response. + * + * @throws Exception + */ + @Test + public void anqpResponseSuccess() throws Exception { + Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>(); + anqpElementMap.put(ANQPElementType.ANQPDomName, + new DomainNameElement(Arrays.asList(new String[] {"test.com"}))); + + ScanDetail scanDetail = createMockScanDetail(); + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(scanDetail); + mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap); + verify(mAnqpCache).addEntry(anqpKey, anqpElementMap); + verify(scanDetail).propagateANQPInfo(anqpElementMap); + } + + /** + * Verify that no ANQP elements will be added to the ANQP cache on receiving a successful + * response for a request that's not sent by us. + * + * @throws Exception + */ + @Test + public void anqpResponseSuccessWithUnknownRequest() throws Exception { + Map<ANQPElementType, ANQPElement> anqpElementMap = new HashMap<>(); + anqpElementMap.put(ANQPElementType.ANQPDomName, + new DomainNameElement(Arrays.asList(new String[] {"test.com"}))); + + when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, true)).thenReturn(null); + mCallbacks.onANQPResponse(TEST_BSSID, anqpElementMap); + verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap()); + } + + /** + * Verify that no ANQP elements will be added to the ANQP cache on receiving a failure response. + * + * @throws Exception + */ + @Test + public void anqpResponseFailure() throws Exception { + ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID, + TEST_ANQP_DOMAIN_ID); + + ScanDetail scanDetail = createMockScanDetail(); + when(mAnqpRequestManager.onRequestCompleted(TEST_BSSID, false)).thenReturn(scanDetail); + mCallbacks.onANQPResponse(TEST_BSSID, null); + verify(mAnqpCache, never()).addEntry(any(ANQPNetworkKey.class), anyMap()); + + } + + /** * Validate the broadcast intent when icon file retrieval succeeded. * * @throws Exception @@ -449,6 +515,9 @@ public class PasspointManagerTest { when(mAnqpCache.getEntry(anqpKey)).thenReturn(null); List<Pair<PasspointProvider, PasspointMatch>> result = mManager.matchProvider(createMockScanDetail()); + // Verify that a request for ANQP elements is initiated. + verify(mAnqpRequestManager).requestANQPElements(eq(TEST_BSSID), any(ScanDetail.class), + anyBoolean(), anyBoolean()); assertTrue(result.isEmpty()); } |