Unbreak extraInfo values returned to apps.

These were broken by aosp/1553463, which made filterNetworkInfo
unconditionally call setDetailedState with a reason of "" and an
extraInfo of null.

Fix both synchronous getter APIs (e.g., getNetworkInfo) and
CONNECTIVITY_ACTION broadcasts by calling a new
filterForLegacyLockdown method that behaves similarly to how the
now-deleted LockdownVpnTracker#augmentNetworkInfo used to behave.

While I'm at it, move back to private a method that was public
only because LockdownVpnTracker used it.

Fix: 181855958
Test: new unit test coverage
Change-Id: I2c7b88fcec9dd36b45cb51db8d19b3ee8bad44a6
This commit is contained in:
Lorenzo Colitti
2021-03-10 00:18:59 +09:00
parent 8affa9ee66
commit e30db8d2a7
2 changed files with 97 additions and 16 deletions

View File

@@ -1489,11 +1489,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
// but only exists if an app asks about them or requests them. Ensure the requesting app // but only exists if an app asks about them or requests them. Ensure the requesting app
// gets the type it asks for. // gets the type it asks for.
filtered.setType(type); filtered.setType(type);
final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked) if (isNetworkWithCapabilitiesBlocked(nc, uid, ignoreBlocked)) {
? DetailedState.BLOCKED filtered.setDetailedState(DetailedState.BLOCKED, null /* reason */,
: filtered.getDetailedState(); null /* extraInfo */);
filtered.setDetailedState(getLegacyLockdownState(state), }
"" /* reason */, null /* extraInfo */); filterForLegacyLockdown(filtered);
return filtered; return filtered;
} }
@@ -1569,8 +1569,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false) final DetailedState state = isNetworkWithCapabilitiesBlocked(nc, uid, false)
? DetailedState.BLOCKED ? DetailedState.BLOCKED
: DetailedState.DISCONNECTED; : DetailedState.DISCONNECTED;
info.setDetailedState(getLegacyLockdownState(state), info.setDetailedState(state, null /* reason */, null /* extraInfo */);
"" /* reason */, null /* extraInfo */); filterForLegacyLockdown(info);
return info; return info;
} }
@@ -2364,9 +2364,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService");
} }
// Public because it's used by mLockdownTracker. private void sendConnectedBroadcast(NetworkInfo info) {
public void sendConnectedBroadcast(NetworkInfo info) {
PermissionUtils.enforceNetworkStackPermission(mContext);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION); sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
} }
@@ -5012,8 +5010,8 @@ public class ConnectivityService extends IConnectivityManager.Stub
// The legacy lockdown VPN always uses the default network. // The legacy lockdown VPN always uses the default network.
// If the VPN's underlying network is no longer the current default network, it means that // If the VPN's underlying network is no longer the current default network, it means that
// the default network has just switched, and the VPN is about to disconnect. // the default network has just switched, and the VPN is about to disconnect.
// Report that the VPN is not connected, so when the state of NetworkInfo objects // Report that the VPN is not connected, so the state of NetworkInfo objects overwritten
// overwritten by getLegacyLockdownState will be set to CONNECTING and not CONNECTED. // by filterForLegacyLockdown will be set to CONNECTING and not CONNECTED.
final NetworkAgentInfo defaultNetwork = getDefaultNetwork(); final NetworkAgentInfo defaultNetwork = getDefaultNetwork();
if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) { if (defaultNetwork == null || !defaultNetwork.network.equals(underlying[0])) {
return null; return null;
@@ -5022,6 +5020,9 @@ public class ConnectivityService extends IConnectivityManager.Stub
return nai; return nai;
}; };
// TODO: move all callers to filterForLegacyLockdown and delete this method.
// This likely requires making sendLegacyNetworkBroadcast take a NetworkInfo object instead of
// just a DetailedState object.
private DetailedState getLegacyLockdownState(DetailedState origState) { private DetailedState getLegacyLockdownState(DetailedState origState) {
if (origState != DetailedState.CONNECTED) { if (origState != DetailedState.CONNECTED) {
return origState; return origState;
@@ -5031,6 +5032,23 @@ public class ConnectivityService extends IConnectivityManager.Stub
: DetailedState.CONNECTED; : DetailedState.CONNECTED;
} }
private void filterForLegacyLockdown(NetworkInfo ni) {
if (!mLockdownEnabled || !ni.isConnected()) return;
// The legacy lockdown VPN replaces the state of every network in CONNECTED state with the
// state of its VPN. This is to ensure that when an underlying network connects, apps will
// not see a CONNECTIVITY_ACTION broadcast for a network in state CONNECTED until the VPN
// comes up, at which point there is a new CONNECTIVITY_ACTION broadcast for the underlying
// network, this time with a state of CONNECTED.
//
// Now that the legacy lockdown code lives in ConnectivityService, and no longer has access
// to the internal state of the Vpn object, always replace the state with CONNECTING. This
// is not too far off the truth, since an always-on VPN, when not connected, is always
// trying to reconnect.
if (getLegacyLockdownNai() == null) {
ni.setDetailedState(DetailedState.CONNECTING, "", null);
}
}
@Override @Override
public void setProvisioningNotificationVisible(boolean visible, int networkType, public void setProvisioningNotificationVisible(boolean visible, int networkType,
String action) { String action) {
@@ -7914,6 +7932,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// and is still connected. // and is still connected.
NetworkInfo info = new NetworkInfo(nai.networkInfo); NetworkInfo info = new NetworkInfo(nai.networkInfo);
info.setType(type); info.setType(type);
filterForLegacyLockdown(info);
if (state != DetailedState.DISCONNECTED) { if (state != DetailedState.DISCONNECTED) {
info.setDetailedState(state, null, info.getExtraInfo()); info.setDetailedState(state, null, info.getExtraInfo());
sendConnectedBroadcast(info); sendConnectedBroadcast(info);

View File

@@ -1743,11 +1743,29 @@ public class ConnectivityServiceTest {
return expected; return expected;
} }
private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) {
final DetailedState state = ni.getDetailedState();
if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false;
// Expect a null extraInfo if the network is CONNECTING, because a CONNECTIVITY_ACTION
// broadcast with a state of CONNECTING only happens due to legacy VPN lockdown, which also
// nulls out extraInfo.
if (state == DetailedState.CONNECTING && ni.getExtraInfo() != null) return false;
// Can't make any assertions about DISCONNECTED broadcasts. When a network actually
// disconnects, disconnectAndDestroyNetwork sets its state to DISCONNECTED and its extraInfo
// to null. But if the DISCONNECTED broadcast is just simulated by LegacyTypeTracker due to
// a network switch, extraInfo will likely be populated.
// This is likely a bug in CS, but likely not one we can fix without impacting apps.
return true;
}
private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) {
return registerConnectivityBroadcastThat(1, intent -> return registerConnectivityBroadcastThat(1, intent -> {
type == intent.getIntExtra(EXTRA_NETWORK_TYPE, -1) && state.equals( final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
((NetworkInfo) intent.getParcelableExtra(EXTRA_NETWORK_INFO)) final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
.getDetailedState())); return type == actualType
&& state == ni.getDetailedState()
&& extraInfoInBroadcastHasExpectedNullness(ni);
});
} }
@Test @Test
@@ -7185,12 +7203,14 @@ public class ConnectivityServiceTest {
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
setUidRulesChanged(RULE_REJECT_ALL); setUidRulesChanged(RULE_REJECT_ALL);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
assertNull(mCm.getActiveNetwork()); assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
// ConnectivityService should cache it not to invoke the callback again. // ConnectivityService should cache it not to invoke the callback again.
setUidRulesChanged(RULE_REJECT_METERED); setUidRulesChanged(RULE_REJECT_METERED);
@@ -7201,12 +7221,14 @@ public class ConnectivityServiceTest {
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
setUidRulesChanged(RULE_REJECT_METERED); setUidRulesChanged(RULE_REJECT_METERED);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
assertNull(mCm.getActiveNetwork()); assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
// Restrict the network based on UID rule and NOT_METERED capability change. // Restrict the network based on UID rule and NOT_METERED capability change.
mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
@@ -7215,6 +7237,7 @@ public class ConnectivityServiceTest {
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED);
cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
@@ -7223,12 +7246,14 @@ public class ConnectivityServiceTest {
assertNull(mCm.getActiveNetwork()); assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
setUidRulesChanged(RULE_ALLOW_METERED); setUidRulesChanged(RULE_ALLOW_METERED);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
setUidRulesChanged(RULE_NONE); setUidRulesChanged(RULE_NONE);
cellNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback();
@@ -7239,6 +7264,7 @@ public class ConnectivityServiceTest {
assertNull(mCm.getActiveNetwork()); assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
setRestrictBackgroundChanged(true); setRestrictBackgroundChanged(true);
cellNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback();
@@ -7246,12 +7272,14 @@ public class ConnectivityServiceTest {
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
setRestrictBackgroundChanged(false); setRestrictBackgroundChanged(false);
cellNetworkCallback.assertNoCallback(); cellNetworkCallback.assertNoCallback();
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
mCm.unregisterNetworkCallback(cellNetworkCallback); mCm.unregisterNetworkCallback(cellNetworkCallback);
} }
@@ -7310,6 +7338,15 @@ public class ConnectivityServiceTest {
assertNotNull(ni); assertNotNull(ni);
assertEquals(type, ni.getType()); assertEquals(type, ni.getType());
assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState());
if (state == DetailedState.CONNECTED || state == DetailedState.SUSPENDED) {
assertNotNull(ni.getExtraInfo());
} else {
// Technically speaking, a network that's in CONNECTING state will generally have a
// non-null extraInfo. This doesn't actually happen in this test because it never calls
// a legacy API while a network is connecting. When a network is in CONNECTING state
// because of legacy lockdown VPN, its extraInfo is always null.
assertNull(ni.getExtraInfo());
}
} }
private void assertActiveNetworkInfo(int type, DetailedState state) { private void assertActiveNetworkInfo(int type, DetailedState state) {
@@ -7319,6 +7356,26 @@ public class ConnectivityServiceTest {
checkNetworkInfo(mCm.getNetworkInfo(type), type, state); checkNetworkInfo(mCm.getNetworkInfo(type), type, state);
} }
private void assertExtraInfoFromCm(TestNetworkAgentWrapper network, boolean present) {
final NetworkInfo niForNetwork = mCm.getNetworkInfo(network.getNetwork());
final NetworkInfo niForType = mCm.getNetworkInfo(network.getLegacyType());
if (present) {
assertEquals(network.getExtraInfo(), niForNetwork.getExtraInfo());
assertEquals(network.getExtraInfo(), niForType.getExtraInfo());
} else {
assertNull(niForNetwork.getExtraInfo());
assertNull(niForType.getExtraInfo());
}
}
private void assertExtraInfoFromCmBlocked(TestNetworkAgentWrapper network) {
assertExtraInfoFromCm(network, false);
}
private void assertExtraInfoFromCmPresent(TestNetworkAgentWrapper network) {
assertExtraInfoFromCm(network, true);
}
// Checks that each of the |agents| receive a blocked status change callback with the specified // Checks that each of the |agents| receive a blocked status change callback with the specified
// |blocked| value, in any order. This is needed because when an event affects multiple // |blocked| value, in any order. This is needed because when an event affects multiple
// networks, ConnectivityService does not guarantee the order in which callbacks are fired. // networks, ConnectivityService does not guarantee the order in which callbacks are fired.
@@ -7633,6 +7690,7 @@ public class ConnectivityServiceTest {
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
// TODO: it would be nice if we could simply rely on the production code here, and have // TODO: it would be nice if we could simply rely on the production code here, and have
// LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with // LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with
@@ -7661,6 +7719,7 @@ public class ConnectivityServiceTest {
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR));
assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI));
@@ -7703,6 +7762,7 @@ public class ConnectivityServiceTest {
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mWiFiNetworkAgent);
// The VPN comes up again on wifi. // The VPN comes up again on wifi.
b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED);
@@ -7717,6 +7777,7 @@ public class ConnectivityServiceTest {
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); assertTrue(vpnNc.hasTransport(TRANSPORT_VPN));
assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI));
@@ -7733,6 +7794,7 @@ public class ConnectivityServiceTest {
assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
mWiFiNetworkAgent.disconnect(); mWiFiNetworkAgent.disconnect();