Merge "Verify if the VPN app has been added into power save whilte list" into tm-dev
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 51ff5ec..0bb98f8 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -1300,6 +1300,17 @@
}
/**
+ * Removes the interface name from all entries.
+ * This mutates the original structure in place.
+ * @hide
+ */
+ public void clearInterfaces() {
+ for (int i = 0; i < size; i++) {
+ iface[i] = null;
+ }
+ }
+
+ /**
* Only keep entries that match all specified filters.
*
* <p>This mutates the original structure in place. After this method is called,
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 33b44c8..f19bf4a 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -312,9 +312,12 @@
@Override
public void onAvailable(@NonNull Network network) {
final DelegatingDiscoveryListener wrappedListener = new DelegatingDiscoveryListener(
- network, mBaseListener);
+ network, mBaseListener, mBaseExecutor);
mPerNetworkListeners.put(network, wrappedListener);
- discoverServices(mServiceType, mProtocolType, network, mBaseExecutor,
+ // Run discovery callbacks inline on the service handler thread, which is the
+ // same thread used by this NetworkCallback, but DelegatingDiscoveryListener will
+ // use the base executor to run the wrapped callbacks.
+ discoverServices(mServiceType, mProtocolType, network, Runnable::run,
wrappedListener);
}
@@ -334,7 +337,8 @@
public void start(@NonNull NetworkRequest request) {
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
cm.registerNetworkCallback(request, mNetworkCb, mHandler);
- mHandler.post(() -> mBaseListener.onDiscoveryStarted(mServiceType));
+ mHandler.post(() -> mBaseExecutor.execute(() ->
+ mBaseListener.onDiscoveryStarted(mServiceType)));
}
/**
@@ -351,7 +355,7 @@
final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
cm.unregisterNetworkCallback(mNetworkCb);
if (mPerNetworkListeners.size() == 0) {
- mBaseListener.onDiscoveryStopped(mServiceType);
+ mBaseExecutor.execute(() -> mBaseListener.onDiscoveryStopped(mServiceType));
return;
}
for (int i = 0; i < mPerNetworkListeners.size(); i++) {
@@ -399,14 +403,23 @@
}
}
+ /**
+ * A listener wrapping calls to an app-provided listener, while keeping track of found
+ * services, so they can all be reported lost when the underlying network is lost.
+ *
+ * This should be registered to run on the service handler.
+ */
private class DelegatingDiscoveryListener implements DiscoveryListener {
private final Network mNetwork;
private final DiscoveryListener mWrapped;
+ private final Executor mWrappedExecutor;
private final ArraySet<TrackedNsdInfo> mFoundInfo = new ArraySet<>();
- private DelegatingDiscoveryListener(Network network, DiscoveryListener listener) {
+ private DelegatingDiscoveryListener(Network network, DiscoveryListener listener,
+ Executor executor) {
mNetwork = network;
mWrapped = listener;
+ mWrappedExecutor = executor;
}
void notifyAllServicesLost() {
@@ -415,7 +428,7 @@
final NsdServiceInfo serviceInfo = new NsdServiceInfo(
trackedInfo.mServiceName, trackedInfo.mServiceType);
serviceInfo.setNetwork(mNetwork);
- mWrapped.onServiceLost(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
}
}
@@ -444,7 +457,7 @@
// Do not report onStopDiscoveryFailed when some underlying listeners failed:
// this does not mean that all listeners did, and onStopDiscoveryFailed is not
// actionable anyway. Just report that discovery stopped.
- mWrapped.onDiscoveryStopped(serviceType);
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
}
}
@@ -452,20 +465,20 @@
public void onDiscoveryStopped(String serviceType) {
mPerNetworkListeners.remove(mNetwork);
if (mStopRequested && mPerNetworkListeners.size() == 0) {
- mWrapped.onDiscoveryStopped(serviceType);
+ mWrappedExecutor.execute(() -> mWrapped.onDiscoveryStopped(serviceType));
}
}
@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
mFoundInfo.add(new TrackedNsdInfo(serviceInfo));
- mWrapped.onServiceFound(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceFound(serviceInfo));
}
@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
mFoundInfo.remove(new TrackedNsdInfo(serviceInfo));
- mWrapped.onServiceLost(serviceInfo);
+ mWrappedExecutor.execute(() -> mWrapped.onServiceLost(serviceInfo));
}
}
}
@@ -648,8 +661,12 @@
@Override
public void handleMessage(Message message) {
+ // Do not use message in the executor lambdas, as it will be recycled once this method
+ // returns. Keep references to its content instead.
final int what = message.what;
+ final int errorCode = message.arg1;
final int key = message.arg2;
+ final Object obj = message.obj;
final Object listener;
final NsdServiceInfo ns;
final Executor executor;
@@ -659,7 +676,7 @@
executor = mExecutorMap.get(key);
}
if (listener == null) {
- Log.d(TAG, "Stale key " + message.arg2);
+ Log.d(TAG, "Stale key " + key);
return;
}
if (DBG) {
@@ -667,28 +684,28 @@
}
switch (what) {
case DISCOVER_SERVICES_STARTED:
- final String s = getNsdServiceInfoType((NsdServiceInfo) message.obj);
+ final String s = getNsdServiceInfoType((NsdServiceInfo) obj);
executor.execute(() -> ((DiscoveryListener) listener).onDiscoveryStarted(s));
break;
case DISCOVER_SERVICES_FAILED:
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStartDiscoveryFailed(
- getNsdServiceInfoType(ns), message.arg1));
+ getNsdServiceInfoType(ns), errorCode));
break;
case SERVICE_FOUND:
executor.execute(() -> ((DiscoveryListener) listener).onServiceFound(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case SERVICE_LOST:
executor.execute(() -> ((DiscoveryListener) listener).onServiceLost(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case STOP_DISCOVERY_FAILED:
// TODO: failure to stop discovery should be internal and retried internally, as
// the effect for the client is indistinguishable from STOP_DISCOVERY_SUCCEEDED
removeListener(key);
executor.execute(() -> ((DiscoveryListener) listener).onStopDiscoveryFailed(
- getNsdServiceInfoType(ns), message.arg1));
+ getNsdServiceInfoType(ns), errorCode));
break;
case STOP_DISCOVERY_SUCCEEDED:
removeListener(key);
@@ -698,33 +715,33 @@
case REGISTER_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onRegistrationFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case REGISTER_SERVICE_SUCCEEDED:
executor.execute(() -> ((RegistrationListener) listener).onServiceRegistered(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
case UNREGISTER_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onUnregistrationFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case UNREGISTER_SERVICE_SUCCEEDED:
// TODO: do not unregister listener until service is unregistered, or provide
// alternative way for unregistering ?
- removeListener(message.arg2);
+ removeListener(key);
executor.execute(() -> ((RegistrationListener) listener).onServiceUnregistered(
ns));
break;
case RESOLVE_SERVICE_FAILED:
removeListener(key);
executor.execute(() -> ((ResolveListener) listener).onResolveFailed(
- ns, message.arg1));
+ ns, errorCode));
break;
case RESOLVE_SERVICE_SUCCEEDED:
removeListener(key);
executor.execute(() -> ((ResolveListener) listener).onServiceResolved(
- (NsdServiceInfo) message.obj));
+ (NsdServiceInfo) obj));
break;
default:
Log.d(TAG, "Ignored " + message);
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index a68f6b3..42a108f 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
import static android.content.Intent.ACTION_USER_REMOVED;
import static android.content.Intent.EXTRA_UID;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.IFACE_ALL;
@@ -172,6 +173,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
@@ -343,11 +345,16 @@
@GuardedBy("mStatsLock")
private String mActiveIface;
- /** Set of any ifaces associated with mobile networks since boot. */
+ /** Set of all ifaces currently associated with mobile networks. */
private volatile String[] mMobileIfaces = new String[0];
- /** Set of any ifaces associated with wifi networks since boot. */
- private volatile String[] mWifiIfaces = new String[0];
+ /* A set of all interfaces that have ever been associated with mobile networks since boot. */
+ @GuardedBy("mStatsLock")
+ private final Set<String> mAllMobileIfacesSinceBoot = new ArraySet<>();
+
+ /* A set of all interfaces that have ever been associated with wifi networks since boot. */
+ @GuardedBy("mStatsLock")
+ private final Set<String> mAllWifiIfacesSinceBoot = new ArraySet<>();
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
@GuardedBy("mStatsLock")
@@ -1564,17 +1571,37 @@
return dataLayer;
}
+ private String[] getAllIfacesSinceBoot(int transport) {
+ synchronized (mStatsLock) {
+ final Set<String> ifaceSet;
+ if (transport == TRANSPORT_WIFI) {
+ ifaceSet = mAllWifiIfacesSinceBoot;
+ } else if (transport == TRANSPORT_CELLULAR) {
+ ifaceSet = mAllMobileIfacesSinceBoot;
+ } else {
+ throw new IllegalArgumentException("Invalid transport " + transport);
+ }
+
+ return ifaceSet.toArray(new String[0]);
+ }
+ }
+
@Override
public NetworkStats getUidStatsForTransport(int transport) {
PermissionUtils.enforceNetworkStackPermission(mContext);
try {
- final String[] relevantIfaces =
- transport == TRANSPORT_WIFI ? mWifiIfaces : mMobileIfaces;
+ final String[] ifaceArray = getAllIfacesSinceBoot(transport);
// TODO(b/215633405) : mMobileIfaces and mWifiIfaces already contain the stacked
// interfaces, so this is not useful, remove it.
final String[] ifacesToQuery =
- mStatsFactory.augmentWithStackedInterfaces(relevantIfaces);
- return getNetworkStatsUidDetail(ifacesToQuery);
+ mStatsFactory.augmentWithStackedInterfaces(ifaceArray);
+ final NetworkStats stats = getNetworkStatsUidDetail(ifacesToQuery);
+ // Clear the interfaces of the stats before returning, so callers won't get this
+ // information. This is because no caller needs this information for now, and it
+ // makes it easier to change the implementation later by using the histories in the
+ // recorder.
+ stats.clearInterfaces();
+ return stats;
} catch (RemoteException e) {
Log.wtf(TAG, "Error compiling UID stats", e);
return new NetworkStats(0L, 0);
@@ -1955,7 +1982,6 @@
final boolean combineSubtypeEnabled = mSettings.getCombineSubtypeEnabled();
final ArraySet<String> mobileIfaces = new ArraySet<>();
- final ArraySet<String> wifiIfaces = new ArraySet<>();
for (NetworkStateSnapshot snapshot : snapshots) {
final int displayTransport =
getDisplayTransport(snapshot.getNetworkCapabilities().getTransportTypes());
@@ -2000,9 +2026,12 @@
if (isMobile) {
mobileIfaces.add(baseIface);
+ // If the interface name was present in the wifi set, the interface won't
+ // be removed from it to prevent stats from getting rollback.
+ mAllMobileIfacesSinceBoot.add(baseIface);
}
if (isWifi) {
- wifiIfaces.add(baseIface);
+ mAllWifiIfacesSinceBoot.add(baseIface);
}
}
@@ -2044,9 +2073,10 @@
findOrCreateNetworkIdentitySet(mActiveUidIfaces, iface).add(ident);
if (isMobile) {
mobileIfaces.add(iface);
+ mAllMobileIfacesSinceBoot.add(iface);
}
if (isWifi) {
- wifiIfaces.add(iface);
+ mAllWifiIfacesSinceBoot.add(iface);
}
mStatsFactory.noteStackedIface(iface, baseIface);
@@ -2055,16 +2085,11 @@
}
mMobileIfaces = mobileIfaces.toArray(new String[0]);
- mWifiIfaces = wifiIfaces.toArray(new String[0]);
// TODO (b/192758557): Remove debug log.
if (CollectionUtils.contains(mMobileIfaces, null)) {
throw new NullPointerException(
"null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
}
- if (CollectionUtils.contains(mWifiIfaces, null)) {
- throw new NullPointerException(
- "null element in mWifiIfaces: " + Arrays.toString(mWifiIfaces));
- }
}
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
@@ -2532,6 +2557,22 @@
}
pw.decreaseIndent();
+ pw.println("All wifi interfaces:");
+ pw.increaseIndent();
+ for (String iface : mAllWifiIfacesSinceBoot) {
+ pw.print(iface + " ");
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("All mobile interfaces:");
+ pw.increaseIndent();
+ for (String iface : mAllMobileIfacesSinceBoot) {
+ pw.print(iface + " ");
+ }
+ pw.println();
+ pw.decreaseIndent();
+
// Get the top openSession callers
final HashMap calls;
synchronized (mOpenSessionCallsLock) {
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 33a0a83..69ec189 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -22,6 +22,7 @@
import android.net.Network
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
@@ -45,7 +46,9 @@
import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo
+import android.os.Handler
import android.os.HandlerThread
+import android.os.Process.myTid
import android.platform.test.annotations.AppModeFull
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
@@ -62,6 +65,7 @@
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.net.ServerSocket
@@ -111,12 +115,20 @@
private interface NsdEvent
private open class NsdRecord<T : NsdEvent> private constructor(
- private val history: ArrayTrackRecord<T>
+ private val history: ArrayTrackRecord<T>,
+ private val expectedThreadId: Int? = null
) : TrackRecord<T> by history {
- constructor() : this(ArrayTrackRecord())
+ constructor(expectedThreadId: Int? = null) : this(ArrayTrackRecord(), expectedThreadId)
val nextEvents = history.newReadHead()
+ override fun add(e: T): Boolean {
+ if (expectedThreadId != null) {
+ assertEquals(expectedThreadId, myTid(), "Callback is running on the wrong thread")
+ }
+ return history.add(e)
+ }
+
inline fun <reified V : NsdEvent> expectCallbackEventually(
crossinline predicate: (V) -> Boolean = { true }
): V = nextEvents.poll(TIMEOUT_MS) { e -> e is V && predicate(e) } as V?
@@ -136,8 +148,8 @@
}
}
- private class NsdRegistrationRecord : RegistrationListener,
- NsdRecord<NsdRegistrationRecord.RegistrationEvent>() {
+ private class NsdRegistrationRecord(expectedThreadId: Int? = null) : RegistrationListener,
+ NsdRecord<NsdRegistrationRecord.RegistrationEvent>(expectedThreadId) {
sealed class RegistrationEvent : NsdEvent {
abstract val serviceInfo: NsdServiceInfo
@@ -174,8 +186,8 @@
}
}
- private class NsdDiscoveryRecord : DiscoveryListener,
- NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>() {
+ private class NsdDiscoveryRecord(expectedThreadId: Int? = null) :
+ DiscoveryListener, NsdRecord<NsdDiscoveryRecord.DiscoveryEvent>(expectedThreadId) {
sealed class DiscoveryEvent : NsdEvent {
data class StartDiscoveryFailed(val serviceType: String, val errorCode: Int)
: DiscoveryEvent()
@@ -452,7 +464,7 @@
}
}
- @Test
+ @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
fun testNsdManager_DiscoverWithNetworkRequest() {
// This test requires shims supporting T+ APIs (discovering on network request)
assumeTrue(TestUtils.shouldTestTApis())
@@ -462,9 +474,12 @@
si.serviceName = this.serviceName
si.port = 12345 // Test won't try to connect so port does not matter
- val registrationRecord = NsdRegistrationRecord()
- val registeredInfo1 = registerService(registrationRecord, si)
- val discoveryRecord = NsdDiscoveryRecord()
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val registrationRecord = NsdRegistrationRecord(expectedThreadId = handlerThread.threadId)
+ val registeredInfo1 = registerService(registrationRecord, si, executor)
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
tryTest {
val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
@@ -474,7 +489,7 @@
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(specifier)
.build(),
- Executor { it.run() }, discoveryRecord)
+ executor, discoveryRecord)
val discoveryStarted = discoveryRecord.expectCallback<DiscoveryStarted>()
assertEquals(SERVICE_TYPE, discoveryStarted.serviceType)
@@ -490,7 +505,7 @@
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceLost1.serviceInfo))
registrationRecord.expectCallback<ServiceUnregistered>()
- val registeredInfo2 = registerService(registrationRecord, si)
+ val registeredInfo2 = registerService(registrationRecord, si, executor)
val serviceDiscovered2 = discoveryRecord.expectCallback<ServiceFound>()
assertEquals(registeredInfo2.serviceName, serviceDiscovered2.serviceInfo.serviceName)
assertEquals(testNetwork1.network, nsdShim.getNetwork(serviceDiscovered2.serviceInfo))
@@ -517,6 +532,39 @@
}
}
+ @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
+ fun testNsdManager_DiscoverWithNetworkRequest_NoMatchingNetwork() {
+ // This test requires shims supporting T+ APIs (discovering on network request)
+ assumeTrue(TestUtils.shouldTestTApis())
+
+ val si = NsdServiceInfo()
+ si.serviceType = SERVICE_TYPE
+ si.serviceName = this.serviceName
+ si.port = 12345 // Test won't try to connect so port does not matter
+
+ val handler = Handler(handlerThread.looper)
+ val executor = Executor { handler.post(it) }
+
+ val discoveryRecord = NsdDiscoveryRecord(expectedThreadId = handlerThread.threadId)
+ val specifier = TestNetworkSpecifier(testNetwork1.iface.interfaceName)
+
+ tryTest {
+ nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_TRUSTED)
+ .addTransportType(TRANSPORT_TEST)
+ // Specified network does not have this capability
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .setNetworkSpecifier(specifier)
+ .build(),
+ executor, discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStarted>()
+ } cleanup {
+ nsdManager.stopServiceDiscovery(discoveryRecord)
+ discoveryRecord.expectCallback<DiscoveryStopped>()
+ }
+ }
+
@Test
fun testNsdManager_ResolveOnNetwork() {
// This test requires shims supporting T+ APIs (NsdServiceInfo.network)
@@ -648,9 +696,12 @@
/**
* Register a service and return its registration record.
*/
- private fun registerService(record: NsdRegistrationRecord, si: NsdServiceInfo): NsdServiceInfo {
- nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, Executor { it.run() },
- record)
+ private fun registerService(
+ record: NsdRegistrationRecord,
+ si: NsdServiceInfo,
+ executor: Executor = Executor { it.run() }
+ ): NsdServiceInfo {
+ nsdShim.registerService(nsdManager, si, NsdManager.PROTOCOL_DNS_SD, executor, record)
// We may not always get the name that we tried to register;
// This events tells us the name that was registered.
val cb = record.expectCallback<ServiceRegistered>()
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 9293e5c..b1d44ea 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -1149,7 +1149,7 @@
// already documented publicly, refer to {@link NetworkStatsManager#queryDetails}.
}
- @Test
+ @Test @Ignore // TODO(b/234099453): re-enable when the prebuilt module is updated
public void testUidStatsForTransport() throws Exception {
// pretend that network comes online
expectDefaultSettings();
@@ -1180,9 +1180,12 @@
assertEquals(3, stats.size());
entry1.operations = 1;
+ entry1.iface = null;
assertEquals(entry1, stats.getValues(0, null));
entry2.operations = 1;
+ entry2.iface = null;
assertEquals(entry2, stats.getValues(1, null));
+ entry3.iface = null;
assertEquals(entry3, stats.getValues(2, null));
}