Merge "Support for Venue URL and friendly name from Network agent"

This commit is contained in:
Hai Shalom
2021-01-13 01:54:05 +00:00
committed by Gerrit Code Review
5 changed files with 246 additions and 18 deletions

View File

@@ -39,9 +39,11 @@ public final class CaptivePortalData implements Parcelable {
private final long mByteLimit;
private final long mExpiryTimeMillis;
private final boolean mCaptive;
private final String mVenueFriendlyName;
private CaptivePortalData(long refreshTimeMillis, Uri userPortalUrl, Uri venueInfoUrl,
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive) {
boolean isSessionExtendable, long byteLimit, long expiryTimeMillis, boolean captive,
String venueFriendlyName) {
mRefreshTimeMillis = refreshTimeMillis;
mUserPortalUrl = userPortalUrl;
mVenueInfoUrl = venueInfoUrl;
@@ -49,11 +51,12 @@ public final class CaptivePortalData implements Parcelable {
mByteLimit = byteLimit;
mExpiryTimeMillis = expiryTimeMillis;
mCaptive = captive;
mVenueFriendlyName = venueFriendlyName;
}
private CaptivePortalData(Parcel p) {
this(p.readLong(), p.readParcelable(null), p.readParcelable(null), p.readBoolean(),
p.readLong(), p.readLong(), p.readBoolean());
p.readLong(), p.readLong(), p.readBoolean(), p.readString());
}
@Override
@@ -70,6 +73,7 @@ public final class CaptivePortalData implements Parcelable {
dest.writeLong(mByteLimit);
dest.writeLong(mExpiryTimeMillis);
dest.writeBoolean(mCaptive);
dest.writeString(mVenueFriendlyName);
}
/**
@@ -83,6 +87,7 @@ public final class CaptivePortalData implements Parcelable {
private long mBytesRemaining = -1;
private long mExpiryTime = -1;
private boolean mCaptive;
private String mVenueFriendlyName;
/**
* Create an empty builder.
@@ -100,7 +105,8 @@ public final class CaptivePortalData implements Parcelable {
.setSessionExtendable(data.mIsSessionExtendable)
.setBytesRemaining(data.mByteLimit)
.setExpiryTime(data.mExpiryTimeMillis)
.setCaptive(data.mCaptive);
.setCaptive(data.mCaptive)
.setVenueFriendlyName(data.mVenueFriendlyName);
}
/**
@@ -166,13 +172,23 @@ public final class CaptivePortalData implements Parcelable {
return this;
}
/**
* Set the venue friendly name.
*/
@NonNull
public Builder setVenueFriendlyName(@Nullable String venueFriendlyName) {
mVenueFriendlyName = venueFriendlyName;
return this;
}
/**
* Create a new {@link CaptivePortalData}.
*/
@NonNull
public CaptivePortalData build() {
return new CaptivePortalData(mRefreshTime, mUserPortalUrl, mVenueInfoUrl,
mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive);
mIsSessionExtendable, mBytesRemaining, mExpiryTime, mCaptive,
mVenueFriendlyName);
}
}
@@ -232,6 +248,14 @@ public final class CaptivePortalData implements Parcelable {
return mCaptive;
}
/**
* Get the venue friendly name
*/
@Nullable
public String getVenueFriendlyName() {
return mVenueFriendlyName;
}
@NonNull
public static final Creator<CaptivePortalData> CREATOR = new Creator<CaptivePortalData>() {
@Override
@@ -248,7 +272,7 @@ public final class CaptivePortalData implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mRefreshTimeMillis, mUserPortalUrl, mVenueInfoUrl,
mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive);
mIsSessionExtendable, mByteLimit, mExpiryTimeMillis, mCaptive, mVenueFriendlyName);
}
@Override
@@ -261,7 +285,8 @@ public final class CaptivePortalData implements Parcelable {
&& mIsSessionExtendable == other.mIsSessionExtendable
&& mByteLimit == other.mByteLimit
&& mExpiryTimeMillis == other.mExpiryTimeMillis
&& mCaptive == other.mCaptive;
&& mCaptive == other.mCaptive
&& Objects.equals(mVenueFriendlyName, other.mVenueFriendlyName);
}
@Override
@@ -274,6 +299,7 @@ public final class CaptivePortalData implements Parcelable {
+ ", byteLimit: " + mByteLimit
+ ", expiryTime: " + mExpiryTimeMillis
+ ", captive: " + mCaptive
+ ", venueFriendlyName: " + mVenueFriendlyName
+ "}";
}
}

View File

@@ -2973,7 +2973,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
case EVENT_CAPPORT_DATA_CHANGED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
handleCaptivePortalDataUpdate(nai, (CaptivePortalData) msg.obj);
handleCapportApiDataUpdate(nai, (CaptivePortalData) msg.obj);
break;
}
}
@@ -3311,9 +3311,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
private void handleCaptivePortalDataUpdate(@NonNull final NetworkAgentInfo nai,
private void handleCapportApiDataUpdate(@NonNull final NetworkAgentInfo nai,
@Nullable final CaptivePortalData data) {
nai.captivePortalData = data;
nai.capportApiData = data;
// CaptivePortalData will be merged into LinkProperties from NetworkAgentInfo
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
@@ -6123,6 +6123,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
private void processLinkPropertiesFromAgent(NetworkAgentInfo nai, LinkProperties lp) {
lp.ensureDirectlyConnectedRoutes();
nai.clatd.setNat64PrefixFromRa(lp.getNat64Prefix());
nai.networkAgentPortalData = lp.getCaptivePortalData();
}
private void updateLinkProperties(NetworkAgentInfo networkAgent, LinkProperties newLp,
@@ -6166,9 +6167,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
updateWakeOnLan(newLp);
// Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo,
// it is not contained in LinkProperties sent from NetworkAgents so needs to be merged here.
newLp.setCaptivePortalData(networkAgent.captivePortalData);
// Captive portal data is obtained from NetworkMonitor and stored in NetworkAgentInfo.
// It is not always contained in the LinkProperties sent from NetworkAgents, and if it
// does, it needs to be merged here.
newLp.setCaptivePortalData(mergeCaptivePortalData(networkAgent.networkAgentPortalData,
networkAgent.capportApiData));
// TODO - move this check to cover the whole function
if (!Objects.equals(newLp, oldLp)) {
@@ -6188,6 +6191,57 @@ public class ConnectivityService extends IConnectivityManager.Stub
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
}
/**
* @param naData captive portal data from NetworkAgent
* @param apiData captive portal data from capport API
*/
@Nullable
private CaptivePortalData mergeCaptivePortalData(CaptivePortalData naData,
CaptivePortalData apiData) {
if (naData == null || apiData == null) {
return naData == null ? apiData : naData;
}
final CaptivePortalData.Builder captivePortalBuilder =
new CaptivePortalData.Builder(naData);
if (apiData.isCaptive()) {
captivePortalBuilder.setCaptive(true);
}
if (apiData.isSessionExtendable()) {
captivePortalBuilder.setSessionExtendable(true);
}
if (apiData.getExpiryTimeMillis() >= 0 || apiData.getByteLimit() >= 0) {
// Expiry time, bytes remaining, refresh time all need to come from the same source,
// otherwise data would be inconsistent. Prefer the capport API info if present,
// as it can generally be refreshed more often.
captivePortalBuilder.setExpiryTime(apiData.getExpiryTimeMillis());
captivePortalBuilder.setBytesRemaining(apiData.getByteLimit());
captivePortalBuilder.setRefreshTime(apiData.getRefreshTimeMillis());
} else if (naData.getExpiryTimeMillis() < 0 && naData.getByteLimit() < 0) {
// No source has time / bytes remaining information: surface the newest refresh time
// for other fields
captivePortalBuilder.setRefreshTime(
Math.max(naData.getRefreshTimeMillis(), apiData.getRefreshTimeMillis()));
}
// Prioritize the user portal URL from the network agent.
if (apiData.getUserPortalUrl() != null && (naData.getUserPortalUrl() == null
|| TextUtils.isEmpty(naData.getUserPortalUrl().toSafeString()))) {
captivePortalBuilder.setUserPortalUrl(apiData.getUserPortalUrl());
}
// Prioritize the venue information URL from the network agent.
if (apiData.getVenueInfoUrl() != null && (naData.getVenueInfoUrl() == null
|| TextUtils.isEmpty(naData.getVenueInfoUrl().toSafeString()))) {
captivePortalBuilder.setVenueInfoUrl(apiData.getVenueInfoUrl());
// Note that venue friendly name can only come from the network agent because it is not
// in use in RFC8908. However, if using the Capport venue URL, make sure that the
// friendly name is not set from the network agent.
captivePortalBuilder.setVenueFriendlyName(null);
}
return captivePortalBuilder.build();
}
private void wakeupModifyInterface(String iface, NetworkCapabilities caps, boolean add) {
// Marks are only available on WiFi interfaces. Checking for
// marks on unsupported interfaces is harmless.

View File

@@ -189,13 +189,18 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
// Set to true when partial connectivity was detected.
public boolean partialConnectivity;
// Captive portal info of the network, if any.
// Captive portal info of the network from RFC8908, if any.
// Obtained by ConnectivityService and merged into NetworkAgent-provided information.
public CaptivePortalData captivePortalData;
public CaptivePortalData capportApiData;
// The UID of the remote entity that created this Network.
public final int creatorUid;
// Network agent portal info of the network, if any. This information is provided from
// non-RFC8908 sources, such as Wi-Fi Passpoint, which can provide information such as Venue
// URL, Terms & Conditions URL, and network friendly name.
public CaptivePortalData networkAgentPortalData;
// Networks are lingered when they become unneeded as a result of their NetworkRequests being
// satisfied by a higher-scoring network. so as to allow communication to wrap up before the
// network is taken down. This usually only happens to the default network. Lingering ends with

View File

@@ -41,13 +41,14 @@ class CaptivePortalDataTest {
.setBytesRemaining(456L)
.setExpiryTime(789L)
.setCaptive(true)
.setVenueFriendlyName("venue friendly name")
.build()
private fun makeBuilder() = CaptivePortalData.Builder(data)
@Test
fun testParcelUnparcel() {
assertParcelSane(data, fieldCount = 7)
assertParcelSane(data, fieldCount = 8)
assertParcelingIsLossless(makeBuilder().setUserPortalUrl(null).build())
assertParcelingIsLossless(makeBuilder().setVenueInfoUrl(null).build())
@@ -66,6 +67,8 @@ class CaptivePortalDataTest {
assertNotEqualsAfterChange { it.setBytesRemaining(789L) }
assertNotEqualsAfterChange { it.setExpiryTime(12L) }
assertNotEqualsAfterChange { it.setCaptive(false) }
assertNotEqualsAfterChange { it.setVenueFriendlyName("another friendly name") }
assertNotEqualsAfterChange { it.setVenueFriendlyName(null) }
}
@Test
@@ -108,6 +111,11 @@ class CaptivePortalDataTest {
assertFalse(makeBuilder().setCaptive(false).build().isCaptive)
}
@Test
fun testVenueFriendlyName() {
assertEquals("venue friendly name", data.venueFriendlyName)
}
private fun CaptivePortalData.mutate(mutator: (CaptivePortalData.Builder) -> Unit) =
CaptivePortalData.Builder(this).apply { mutator(this) }.build()

View File

@@ -343,6 +343,11 @@ public class ConnectivityServiceTest {
private static final String INTERFACE_NAME = "interface";
private static final String TEST_VENUE_URL_NA = "https://android.com/";
private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/";
private static final String TEST_FRIENDLY_NAME = "Network friendly name";
private static final String TEST_REDIRECT_URL = "http://example.com/firstPath";
private MockContext mServiceContext;
private HandlerThread mCsHandlerThread;
private ConnectivityService.Dependencies mDeps;
@@ -867,7 +872,7 @@ public class ConnectivityServiceTest {
mProbesSucceeded = probesSucceeded;
}
void notifyCaptivePortalDataChanged(CaptivePortalData data) {
void notifyCapportApiDataChanged(CaptivePortalData data) {
try {
mNmCallbacks.notifyCaptivePortalDataChanged(data);
} catch (RemoteException e) {
@@ -2004,7 +2009,7 @@ public class ConnectivityServiceTest {
Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl()));
final CaptivePortalData expectedCapportData = sanitized ? null : capportData;
mWiFiNetworkAgent.notifyCaptivePortalDataChanged(capportData);
mWiFiNetworkAgent.notifyCapportApiDataChanged(capportData);
callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
Objects.equals(expectedCapportData, lp.getCaptivePortalData()));
defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp ->
@@ -3042,7 +3047,7 @@ public class ConnectivityServiceTest {
.setBytesRemaining(12345L)
.build();
mWiFiNetworkAgent.notifyCaptivePortalDataChanged(testData);
mWiFiNetworkAgent.notifyCapportApiDataChanged(testData);
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> testData.equals(lp.getCaptivePortalData()));
@@ -3055,6 +3060,136 @@ public class ConnectivityServiceTest {
lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234);
}
private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception {
// Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks
// with sensitive (captive portal) data
mServiceContext.setPermission(
android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
return captivePortalCallback;
}
private class CaptivePortalTestData {
CaptivePortalTestData(CaptivePortalData naData, CaptivePortalData capportData,
CaptivePortalData expectedMergedData) {
mNaData = naData;
mCapportData = capportData;
mExpectedMergedData = expectedMergedData;
}
public final CaptivePortalData mNaData;
public final CaptivePortalData mCapportData;
public final CaptivePortalData mExpectedMergedData;
}
private CaptivePortalTestData setupCaptivePortalData() {
final CaptivePortalData capportData = new CaptivePortalData.Builder()
.setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
.setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT))
.setExpiryTime(1000000L)
.setBytesRemaining(12345L)
.build();
final CaptivePortalData naData = new CaptivePortalData.Builder()
.setBytesRemaining(80802L)
.setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
.setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
final CaptivePortalData expectedMergedData = new CaptivePortalData.Builder()
.setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL))
.setBytesRemaining(12345L)
.setExpiryTime(1000000L)
.setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA))
.setVenueFriendlyName(TEST_FRIENDLY_NAME).build();
return new CaptivePortalTestData(naData, capportData, expectedMergedData);
}
@Test
public void testMergeCaptivePortalApiWithFriendlyNameAndVenueUrl() throws Exception {
final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
// Baseline capport data
mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
// Venue URL and friendly name from Network agent, confirm that API data gets precedence
// on the bytes remaining.
final LinkProperties linkProperties = new LinkProperties();
linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
mWiFiNetworkAgent.sendLinkProperties(linkProperties);
// Make sure that the capport data is merged
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
// Create a new LP with no Network agent capport data
final LinkProperties newLps = new LinkProperties();
newLps.setMtu(1234);
mWiFiNetworkAgent.sendLinkProperties(newLps);
// CaptivePortalData is not lost and has the original values when LPs are received from the
// NetworkAgent
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())
&& lp.getMtu() == 1234);
// Now send capport data only from the Network agent
mWiFiNetworkAgent.notifyCapportApiDataChanged(null);
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> lp.getCaptivePortalData() == null);
newLps.setCaptivePortalData(captivePortalTestData.mNaData);
mWiFiNetworkAgent.sendLinkProperties(newLps);
// Make sure that only the network agent capport data is available
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
}
@Test
public void testMergeCaptivePortalDataFromNetworkAgentFirstThenCapport() throws Exception {
final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi();
final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData();
// Venue URL and friendly name from Network agent, confirm that API data gets precedence
// on the bytes remaining.
final LinkProperties linkProperties = new LinkProperties();
linkProperties.setCaptivePortalData(captivePortalTestData.mNaData);
mWiFiNetworkAgent.sendLinkProperties(linkProperties);
// Make sure that the data is saved correctly
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mNaData.equals(lp.getCaptivePortalData()));
// Expected merged data: Network agent data is preferred, and values that are not used by
// it are merged from capport data
mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData);
// Make sure that the Capport data is merged correctly
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mExpectedMergedData.equals(lp.getCaptivePortalData()));
// Now set the naData to null
linkProperties.setCaptivePortalData(null);
mWiFiNetworkAgent.sendLinkProperties(linkProperties);
// Make sure that the Capport data is retained correctly
captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent,
lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()));
}
private NetworkRequest.Builder newWifiRequestBuilder() {
return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI);
}