From 0169c9350c355acbfe790859a1cc77a1594b4c1b Mon Sep 17 00:00:00 2001 From: Peter Qiu Date: Tue, 6 Jun 2017 13:12:11 -0700 Subject: hotspot2: anqp: OsuProviderInfo: friendly name and service description selection An OSU provider might contain multiple friendly names and service descriptions in different languages. In this case, the value that's in the default language will be preferred. Bug: 62235301 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: Ie6065a494e6a5c9aa1cc05621ab9627628efb446 --- .../server/wifi/hotspot2/anqp/OsuProviderInfo.java | 43 +++++++ .../wifi/hotspot2/anqp/OsuProviderInfoTest.java | 124 +++++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java b/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java index 85b024e50..8952c5a51 100644 --- a/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java +++ b/service/java/com/android/server/wifi/hotspot2/anqp/OsuProviderInfo.java @@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; /** @@ -168,6 +169,28 @@ public class OsuProviderInfo { return Collections.unmodifiableList(mServiceDescriptions); } + /** + * Return the friendly name string from the friendly name list. The string matching + * the default locale will be returned if it is found, otherwise the first name in the list + * will be returned. A null will be returned if the list is empty. + * + * @return friendly name string + */ + public String getFriendlyName() { + return getI18String(mFriendlyNames); + } + + /** + * Return the service description string from the service description list. The string + * matching the default locale will be returned if it is found, otherwise the first element in + * the list will be returned. A null will be returned if the list is empty. + * + * @return service description string + */ + public String getServiceDescription() { + return getI18String(mServiceDescriptions); + } + @Override public boolean equals(Object thatObject) { if (this == thatObject) { @@ -255,4 +278,24 @@ public class OsuProviderInfo { payload.position(payload.position() + length); return subBuffer; } + + /** + * Return the appropriate I18 string value from the list of I18 string values. + * The string matching the default locale will be returned if it is found, otherwise the + * first string in the list will be returned. A null will be returned if the list is empty. + * + * @param i18Strings List of I18 string values + * @return String matching the default locale, null otherwise + */ + private static String getI18String(List i18Strings) { + for (I18Name name : i18Strings) { + if (name.getLanguage().equals(Locale.getDefault().getLanguage())) { + return name.getText(); + } + } + if (i18Strings.size() > 0) { + return i18Strings.get(0).getText(); + } + return null; + } } diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java index e20d6d53e..8ef93f061 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/anqp/OsuProviderInfoTest.java @@ -25,6 +25,9 @@ import org.junit.Test; import java.net.ProtocolException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; /** * Unit tests for {@link com.android.server.wifi.hotspot2.anqp.OsuProviderInfo}. @@ -80,4 +83,125 @@ public class OsuProviderInfoTest { assertEquals(OsuProviderInfoTestUtil.TEST_OSU_PROVIDER_INFO, OsuProviderInfo.parse(buffer)); } + + /** + * Verify that when a provider contained multiple friendly names in different languages, the + * friendly name that's in default language is returned. + * + * @throws Exception + */ + @Test + public void getFriendlyNameMatchingDefaultLocale() throws Exception { + List friendlyNames = new ArrayList<>(); + Locale defaultLocale = Locale.getDefault(); + Locale nonDefaultLocale = Locale.FRENCH; + if (defaultLocale.equals(nonDefaultLocale)) { + nonDefaultLocale = Locale.ENGLISH; + } + String nonDefaultString = "Non-default"; + String defaultString = "Default"; + friendlyNames.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, nonDefaultString)); + friendlyNames.add(new I18Name(defaultLocale.getLanguage(), defaultLocale, defaultString)); + OsuProviderInfo providerInfo = + new OsuProviderInfo(friendlyNames, null, null, null, null, null); + assertEquals(defaultString, providerInfo.getFriendlyName()); + } + + /** + * Verify that when a provider contained multiple friendly names where no friendly name + * is in default language, the first name in the list is returned. + * + * @throws Exception + */ + @Test + public void getFriendlyNameNotMatchingDefaultLocale() throws Exception { + List friendlyNames = new ArrayList<>(); + Locale nonDefaultLocale = Locale.FRENCH; + if (nonDefaultLocale.equals(Locale.getDefault())) { + nonDefaultLocale = Locale.ENGLISH; + } + String firstString = "First name"; + String secondString = "Second name"; + friendlyNames.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, firstString)); + friendlyNames.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, secondString)); + OsuProviderInfo providerInfo = + new OsuProviderInfo(friendlyNames, null, null, null, null, null); + assertEquals(firstString, providerInfo.getFriendlyName()); + } + + /** + * Verify that null will be returned for a provider containing empty friendly name list. + * + * @throws Exception + */ + @Test + public void getFriendlyNameWithEmptyList() throws Exception { + OsuProviderInfo providerInfo = + new OsuProviderInfo(new ArrayList(), null, null, null, null, null); + assertEquals(null, providerInfo.getFriendlyName()); + } + + /** + * Verify that when a provider contained multiple service descriptions in different languages, + * the service description that's in default language is returned. + * + * @throws Exception + */ + @Test + public void getServiceDescriptionMatchingDefaultLocale() throws Exception { + List serviceDescriptions = new ArrayList<>(); + Locale defaultLocale = Locale.getDefault(); + Locale nonDefaultLocale = Locale.FRENCH; + if (defaultLocale.equals(nonDefaultLocale)) { + nonDefaultLocale = Locale.ENGLISH; + } + String nonDefaultString = "Non-default"; + String defaultString = "Default"; + serviceDescriptions.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, nonDefaultString)); + serviceDescriptions.add( + new I18Name(defaultLocale.getLanguage(), defaultLocale, defaultString)); + OsuProviderInfo providerInfo = + new OsuProviderInfo(null, null, null, null, null, serviceDescriptions); + assertEquals(defaultString, providerInfo.getServiceDescription()); + } + + /** + * Verify that when a provider contained multiple service descriptions where none of them + * is in default language, the first element in the list is returned. + * + * @throws Exception + */ + @Test + public void getServiceDescriptionNotMatchingDefaultLocale() throws Exception { + List serviceDescriptions = new ArrayList<>(); + Locale nonDefaultLocale = Locale.FRENCH; + if (nonDefaultLocale.equals(Locale.getDefault())) { + nonDefaultLocale = Locale.ENGLISH; + } + String firstString = "First name"; + String secondString = "Second name"; + serviceDescriptions.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, firstString)); + serviceDescriptions.add( + new I18Name(nonDefaultLocale.getLanguage(), nonDefaultLocale, secondString)); + OsuProviderInfo providerInfo = + new OsuProviderInfo(null, null, null, null, null, serviceDescriptions); + assertEquals(firstString, providerInfo.getServiceDescription()); + } + + /** + * Verify that null will be returned for a provider containing empty friendly name list. + * + * @throws Exception + */ + @Test + public void getServiceDescriptionWithEmptyList() throws Exception { + OsuProviderInfo providerInfo = + new OsuProviderInfo(null, null, null, null, null, new ArrayList()); + assertEquals(null, providerInfo.getServiceDescription()); + } } -- cgit v1.2.3 From c56add5be84ea0cf85bc77d5efc0494a68466570 Mon Sep 17 00:00:00 2001 From: Peter Qiu Date: Tue, 6 Jun 2017 13:59:04 -0700 Subject: hotspot2: PasspointManager: add support for retrieving OSU providers info The OSU provider icon data is included, will be added when icon data retrieval/caching support is added. Bug: 62235301 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: Ifbddc3c3626063b3c89361c09c7adbf0b054692d --- .../server/wifi/hotspot2/PasspointManager.java | 50 +++++++++++- .../server/wifi/hotspot2/PasspointManagerTest.java | 94 ++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java index f892e7051..3695ba35f 100644 --- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java +++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java @@ -33,6 +33,7 @@ import android.graphics.drawable.Icon; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.UserHandle; import android.text.TextUtils; @@ -47,6 +48,8 @@ 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; +import com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; +import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; import com.android.server.wifi.util.InformationElementUtil; import com.android.server.wifi.util.ScanResultUtil; @@ -439,7 +442,13 @@ public class PasspointManager { InformationElementUtil.getHS2VendorSpecificIE(scanResult.informationElements); // Lookup ANQP data in the cache. - long bssid = Utils.parseMac(scanResult.BSSID); + long bssid; + try { + bssid = Utils.parseMac(scanResult.BSSID); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID); + return new HashMap(); + } ANQPData anqpEntry = mAnqpCache.getEntry(ANQPNetworkKey.buildKey( scanResult.SSID, bssid, scanResult.hessid, vsa.anqpDomainID)); if (anqpEntry != null) { @@ -479,6 +488,45 @@ public class PasspointManager { return config; } + /** + * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given + * AP. + * + * An empty list will be returned when an invalid scan result is provided or no match is found. + * + * @param scanResult The scan result of the AP + * @return List of {@link OsuProvider} + */ + public List getMatchingOsuProviders(ScanResult scanResult) { + if (scanResult == null) { + Log.e(TAG, "Attempt to retrieve OSU providers for a null ScanResult"); + return new ArrayList(); + } + if (!scanResult.isPasspointNetwork()) { + Log.e(TAG, "Attempt to retrieve OSU providers for a non-Passpoint AP"); + return new ArrayList(); + } + + // Lookup OSU Providers ANQP element. + Map anqpElements = getANQPElements(scanResult); + if (!anqpElements.containsKey(Constants.ANQPElementType.HSOSUProviders)) { + return new ArrayList(); + } + + HSOsuProvidersElement element = + (HSOsuProvidersElement) anqpElements.get(Constants.ANQPElementType.HSOSUProviders); + List providers = new ArrayList<>(); + for (OsuProviderInfo info : element.getProviders()) { + // TODO(b/62256482): include icon data once the icon file retrieval and management + // support is added. + OsuProvider provider = new OsuProvider(element.getOsuSsid(), info.getFriendlyName(), + info.getServiceDescription(), info.getServerUri(), + info.getNetworkAccessIdentifier(), info.getMethodList(), null); + providers.add(provider); + } + return providers; + } + /** * Dump the current state of PasspointManager to the provided output stream. * 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 2b871b2db..d2762d022 100644 --- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java @@ -47,10 +47,13 @@ import static org.mockito.MockitoAnnotations.initMocks; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; +import android.net.Uri; import android.net.wifi.EAPConstants; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; +import android.net.wifi.WifiSsid; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSp; @@ -70,6 +73,9 @@ 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 com.android.server.wifi.hotspot2.anqp.HSOsuProvidersElement; +import com.android.server.wifi.hotspot2.anqp.I18Name; +import com.android.server.wifi.hotspot2.anqp.OsuProviderInfo; import com.android.server.wifi.util.ScanResultUtil; import org.junit.Before; @@ -84,6 +90,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -820,6 +827,93 @@ public class PasspointManagerTest { assertNull(mManager.getMatchingWifiConfig(scanResult)); } + /** + * Verify that an empty list will be returned when retrieving OSU providers for an AP with + * null scan result. + * + * @throws Exception + */ + @Test + public void getMatchingOsuProvidersForNullScanResult() throws Exception { + assertTrue(mManager.getMatchingOsuProviders(null).isEmpty()); + } + + /** + * Verify that an empty list will be returned when retrieving OSU providers for an AP with + * invalid BSSID. + * + * @throws Exception + */ + @Test + public void getMatchingOsuProvidersForInvalidBSSID() throws Exception { + ScanResult scanResult = createTestScanResult(); + scanResult.BSSID = "asdfdasfas"; + assertTrue(mManager.getMatchingOsuProviders(scanResult).isEmpty()); + } + + /** + * Verify that an empty list will be returned when retrieving OSU providers for a + * non-Passpoint AP. + * + * @throws Exception + */ + @Test + public void getMatchingOsuProvidersForNonPasspointAP() throws Exception { + ScanResult scanResult = createTestScanResult(); + scanResult.flags = 0; + assertTrue(mManager.getMatchingOsuProviders(scanResult).isEmpty()); + } + + /** + * Verify that an empty list will be returned when no match is found from the ANQP cache. + * + * @throws Exception + */ + @Test + public void getMatchingOsuProviderWithNoMatch() throws Exception { + when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null); + assertTrue(mManager.getMatchingOsuProviders(createTestScanResult()).isEmpty()); + } + + /** + * Verify that an expected provider list will be returned when a match is found from + * the ANQP cache. + * + * @throws Exception + */ + @Test + public void getMatchingOsuProvidersWithMatch() throws Exception { + // Test data. + WifiSsid osuSsid = WifiSsid.createFromAsciiEncoded("Test SSID"); + String friendlyName = "Test Provider"; + String serviceDescription = "Dummy Service"; + Uri serverUri = Uri.parse("https://test.com"); + String nai = "access.test.com"; + List methodList = Arrays.asList(1); + List friendlyNames = Arrays.asList( + new I18Name(Locale.ENGLISH.getLanguage(), Locale.ENGLISH, friendlyName)); + List serviceDescriptions = Arrays.asList( + new I18Name(Locale.ENGLISH.getLanguage(), Locale.ENGLISH, serviceDescription)); + + // Setup OSU providers ANQP element. + List providerInfoList = new ArrayList<>(); + providerInfoList.add(new OsuProviderInfo( + friendlyNames, serverUri, methodList, null, nai, serviceDescriptions)); + Map anqpElementMap = new HashMap<>(); + anqpElementMap.put(ANQPElementType.HSOSUProviders, + new HSOsuProvidersElement(osuSsid, providerInfoList)); + ANQPData entry = new ANQPData(mClock, anqpElementMap); + + // Setup expectation. + OsuProvider provider = new OsuProvider( + osuSsid, friendlyName, serviceDescription, serverUri, nai, methodList, null); + List expectedList = new ArrayList<>(); + expectedList.add(provider); + + when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry); + assertEquals(expectedList, mManager.getMatchingOsuProviders(createTestScanResult())); + } + /** * Verify that the provider list maintained by the PasspointManager after the list is updated * in the data source. -- cgit v1.2.3 From 94ea8c951f0dd81563e7ea22f878397e75487746 Mon Sep 17 00:00:00 2001 From: Peter Qiu Date: Tue, 6 Jun 2017 14:30:47 -0700 Subject: WifiServiceImpl: add support for retrieving Hotspot 2.0 OSU providers For an R2 Passpoint AP, there might be zero or more OSU providers associated it. Add an API to retrieve the associated OSU providers. Bug: 62235301 Test: manual test by exercising this API in WifiTracker and verify the content of the OSU provider Change-Id: I5d9b7d86176bccf39a44f4a39e7f273c2bc2a210 --- .../com/android/server/wifi/WifiServiceImpl.java | 18 +++++++++++++++ .../com/android/server/wifi/WifiStateMachine.java | 27 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java index b33e8be7d..8be41aed1 100644 --- a/service/java/com/android/server/wifi/WifiServiceImpl.java +++ b/service/java/com/android/server/wifi/WifiServiceImpl.java @@ -73,6 +73,7 @@ import android.net.wifi.WifiLinkLayerStats; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; import android.net.wifi.WifiScanner; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.AsyncTask; import android.os.BatteryStats; @@ -1511,6 +1512,23 @@ public class WifiServiceImpl extends IWifiManager.Stub { return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel); } + /** + * Returns list of OSU (Online Sign-Up) providers associated with the given Passpoint network. + * + * @param scanResult scanResult of the Passpoint AP + * @return List of {@link OsuProvider} + */ + @Override + public List getMatchingOsuProviders(ScanResult scanResult) { + enforceAccessPermission(); + mLog.trace("getMatchingOsuProviders uid=%").c(Binder.getCallingUid()).flush(); + if (!mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_WIFI_PASSPOINT)) { + throw new UnsupportedOperationException("Passpoint not enabled"); + } + return mWifiStateMachine.syncGetMatchingOsuProviders(scanResult, mWifiStateMachineChannel); + } + /** * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)} * @return the supplicant-assigned identifier for the new or updated diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index 4560225da..7a16f6982 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -75,6 +75,7 @@ import android.net.wifi.WifiSsid; import android.net.wifi.WpsInfo; import android.net.wifi.WpsResult; import android.net.wifi.WpsResult.Status; +import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; import android.net.wifi.p2p.IWifiP2pManager; import android.os.BatteryStats; @@ -590,6 +591,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss // Get the list of installed Passpoint configurations. static final int CMD_GET_PASSPOINT_CONFIGS = BASE + 108; + // Get the list of OSU providers associated with a Passpoint network. + static final int CMD_GET_MATCHING_OSU_PROVIDERS = BASE + 109; + /* Commands from/to the SupplicantStateTracker */ /* Reset the supplicant state tracker */ static final int CMD_RESET_SUPPLICANT_STATE = BASE + 111; @@ -1863,6 +1867,22 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss return config; } + /** + * Retrieve a list of {@link OsuProvider} associated with the given AP synchronously. + * + * @param scanResult The scan result of the AP + * @param channel Channel for communicating with the state machine + * @return List of {@link OsuProvider} + */ + public List syncGetMatchingOsuProviders(ScanResult scanResult, + AsyncChannel channel) { + Message resultMsg = + channel.sendMessageSynchronously(CMD_GET_MATCHING_OSU_PROVIDERS, scanResult); + List providers = (List) resultMsg.obj; + resultMsg.recycle(); + return providers; + } + /** * Add or update a Passpoint configuration synchronously. * @@ -3883,6 +3903,9 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss case CMD_GET_MATCHING_CONFIG: replyToMessage(message, message.what); break; + case CMD_GET_MATCHING_OSU_PROVIDERS: + replyToMessage(message, message.what, new ArrayList()); + break; case CMD_IP_CONFIGURATION_SUCCESSFUL: case CMD_IP_CONFIGURATION_LOST: case CMD_IP_REACHABILITY_LOST: @@ -4965,6 +4988,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss replyToMessage(message, message.what, mPasspointManager.getMatchingWifiConfig((ScanResult) message.obj)); break; + case CMD_GET_MATCHING_OSU_PROVIDERS: + replyToMessage(message, message.what, + mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj)); + break; case CMD_RECONNECT: mWifiConnectivityManager.forceConnectivityScan(); break; -- cgit v1.2.3