Merge ab/7633965
Bug: 169893837 Merged-In: I3ef19b77bc33546a3e80bca75532d017b4712054 Change-Id: I595fb801f4519177825f3fdc0021fb874a36aa31
This commit is contained in:
@@ -24,6 +24,7 @@ import android.compat.annotation.UnsupportedAppUsage;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
|
|
||||||
@@ -1487,8 +1488,31 @@ public final class NetworkStats implements Parcelable {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recycle.uid == tunUid) {
|
if (tunUid == Process.SYSTEM_UID) {
|
||||||
// Add up traffic through tunUid's underlying interfaces.
|
// Kernel-based VPN or VCN, traffic sent by apps on the VPN/VCN network
|
||||||
|
//
|
||||||
|
// Since the data is not UID-accounted on underlying networks, just use VPN/VCN
|
||||||
|
// network usage as ground truth. Encrypted traffic on the underlying networks will
|
||||||
|
// never be processed here because encrypted traffic on the underlying interfaces
|
||||||
|
// is not present in UID stats, and this method is only called on UID stats.
|
||||||
|
if (tunIface.equals(recycle.iface)) {
|
||||||
|
tunIfaceTotal.add(recycle);
|
||||||
|
underlyingIfacesTotal.add(recycle);
|
||||||
|
|
||||||
|
// In steady state, there should always be one network, but edge cases may
|
||||||
|
// result in the network being null (network lost), and thus no underlying
|
||||||
|
// ifaces is possible.
|
||||||
|
if (perInterfaceTotal.length > 0) {
|
||||||
|
// While platform VPNs and VCNs have exactly one underlying network, that
|
||||||
|
// network may have multiple interfaces (eg for 464xlat). This layer does
|
||||||
|
// not have the required information to identify which of the interfaces
|
||||||
|
// were used. Select "any" of the interfaces. Since overhead is already
|
||||||
|
// lost, this number is an approximation anyways.
|
||||||
|
perInterfaceTotal[0].add(recycle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (recycle.uid == tunUid) {
|
||||||
|
// VpnService VPN, traffic sent by the VPN app over underlying networks
|
||||||
for (int j = 0; j < underlyingIfaces.size(); j++) {
|
for (int j = 0; j < underlyingIfaces.size(); j++) {
|
||||||
if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
|
if (Objects.equals(underlyingIfaces.get(j), recycle.iface)) {
|
||||||
perInterfaceTotal[j].add(recycle);
|
perInterfaceTotal[j].add(recycle);
|
||||||
@@ -1497,7 +1521,7 @@ public final class NetworkStats implements Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (tunIface.equals(recycle.iface)) {
|
} else if (tunIface.equals(recycle.iface)) {
|
||||||
// Add up all tunIface traffic excluding traffic from the vpn app itself.
|
// VpnService VPN; traffic sent by apps on the VPN network
|
||||||
tunIfaceTotal.add(recycle);
|
tunIfaceTotal.add(recycle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1532,9 +1556,13 @@ public final class NetworkStats implements Parcelable {
|
|||||||
// Consider only entries that go onto the VPN interface.
|
// Consider only entries that go onto the VPN interface.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (uid[i] == tunUid) {
|
|
||||||
|
if (uid[i] == tunUid && tunUid != Process.SYSTEM_UID) {
|
||||||
// Exclude VPN app from the redistribution, as it can choose to create packet
|
// Exclude VPN app from the redistribution, as it can choose to create packet
|
||||||
// streams by writing to itself.
|
// streams by writing to itself.
|
||||||
|
//
|
||||||
|
// However, for platform VPNs, do not exclude the system's usage of the VPN network,
|
||||||
|
// since it is never local-only, and never double counted
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
tmpEntry.uid = uid[i];
|
tmpEntry.uid = uid[i];
|
||||||
@@ -1641,6 +1669,12 @@ public final class NetworkStats implements Parcelable {
|
|||||||
int tunUid,
|
int tunUid,
|
||||||
@NonNull List<String> underlyingIfaces,
|
@NonNull List<String> underlyingIfaces,
|
||||||
@NonNull Entry[] moved) {
|
@NonNull Entry[] moved) {
|
||||||
|
if (tunUid == Process.SYSTEM_UID) {
|
||||||
|
// No traffic recorded on a per-UID basis for in-kernel VPN/VCNs over underlying
|
||||||
|
// networks; thus no traffic to deduct.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < underlyingIfaces.size(); i++) {
|
for (int i = 0; i < underlyingIfaces.size(); i++) {
|
||||||
moved[i].uid = tunUid;
|
moved[i].uid = tunUid;
|
||||||
// Add debug info
|
// Add debug info
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty;
|
|||||||
import android.annotation.SdkConstant;
|
import android.annotation.SdkConstant;
|
||||||
import android.annotation.SdkConstant.SdkConstantType;
|
import android.annotation.SdkConstant.SdkConstantType;
|
||||||
import android.annotation.SystemService;
|
import android.annotation.SystemService;
|
||||||
|
import android.app.compat.CompatChanges;
|
||||||
|
import android.compat.annotation.ChangeId;
|
||||||
|
import android.compat.annotation.EnabledSince;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
@@ -125,6 +128,24 @@ public final class NsdManager {
|
|||||||
private static final String TAG = NsdManager.class.getSimpleName();
|
private static final String TAG = NsdManager.class.getSimpleName();
|
||||||
private static final boolean DBG = false;
|
private static final boolean DBG = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When enabled, apps targeting < Android 12 are considered legacy for
|
||||||
|
* the NSD native daemon.
|
||||||
|
* The platform will only keep the daemon running as long as there are
|
||||||
|
* any legacy apps connected.
|
||||||
|
*
|
||||||
|
* After Android 12, directly communicate with native daemon might not
|
||||||
|
* work since the native damon won't always stay alive.
|
||||||
|
* Use the NSD APIs from NsdManager as the replacement is recommended.
|
||||||
|
* An another alternative could be bundling your own mdns solutions instead of
|
||||||
|
* depending on the system mdns native daemon.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
@ChangeId
|
||||||
|
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
|
||||||
|
public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS = 191844585L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast intent action to indicate whether network service discovery is
|
* Broadcast intent action to indicate whether network service discovery is
|
||||||
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
|
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
|
||||||
@@ -202,6 +223,9 @@ public final class NsdManager {
|
|||||||
/** @hide */
|
/** @hide */
|
||||||
public static final int DAEMON_CLEANUP = BASE + 21;
|
public static final int DAEMON_CLEANUP = BASE + 21;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final int DAEMON_STARTUP = BASE + 22;
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final int ENABLE = BASE + 24;
|
public static final int ENABLE = BASE + 24;
|
||||||
/** @hide */
|
/** @hide */
|
||||||
@@ -232,6 +256,8 @@ public final class NsdManager {
|
|||||||
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
|
EVENT_NAMES.put(RESOLVE_SERVICE, "RESOLVE_SERVICE");
|
||||||
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
|
EVENT_NAMES.put(RESOLVE_SERVICE_FAILED, "RESOLVE_SERVICE_FAILED");
|
||||||
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
|
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
|
||||||
|
EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
|
||||||
|
EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
|
||||||
EVENT_NAMES.put(ENABLE, "ENABLE");
|
EVENT_NAMES.put(ENABLE, "ENABLE");
|
||||||
EVENT_NAMES.put(DISABLE, "DISABLE");
|
EVENT_NAMES.put(DISABLE, "DISABLE");
|
||||||
EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
|
EVENT_NAMES.put(NATIVE_DAEMON_EVENT, "NATIVE_DAEMON_EVENT");
|
||||||
@@ -494,6 +520,12 @@ public final class NsdManager {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
fatal("Interrupted wait at init");
|
fatal("Interrupted wait at init");
|
||||||
}
|
}
|
||||||
|
if (CompatChanges.isChangeEnabled(RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only proactively start the daemon if the target SDK < S, otherwise the internal service
|
||||||
|
// would automatically start/stop the native daemon as needed.
|
||||||
|
mAsyncChannel.sendMessage(DAEMON_STARTUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fatal(String msg) {
|
private static void fatal(String msg) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
private static final String MDNS_TAG = "mDnsConnector";
|
private static final String MDNS_TAG = "mDnsConnector";
|
||||||
|
|
||||||
private static final boolean DBG = true;
|
private static final boolean DBG = true;
|
||||||
private static final long CLEANUP_DELAY_MS = 3000;
|
private static final long CLEANUP_DELAY_MS = 10000;
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final NsdSettings mNsdSettings;
|
private final NsdSettings mNsdSettings;
|
||||||
@@ -82,6 +82,8 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
|
|
||||||
private static final int INVALID_ID = 0;
|
private static final int INVALID_ID = 0;
|
||||||
private int mUniqueId = 1;
|
private int mUniqueId = 1;
|
||||||
|
// The count of the connected legacy clients.
|
||||||
|
private int mLegacyClientCount = 0;
|
||||||
|
|
||||||
private class NsdStateMachine extends StateMachine {
|
private class NsdStateMachine extends StateMachine {
|
||||||
|
|
||||||
@@ -94,19 +96,27 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
return NsdManager.nameOf(what);
|
return NsdManager.nameOf(what);
|
||||||
}
|
}
|
||||||
|
|
||||||
void maybeStartDaemon() {
|
private void maybeStartDaemon() {
|
||||||
mDaemon.maybeStart();
|
mDaemon.maybeStart();
|
||||||
maybeScheduleStop();
|
maybeScheduleStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void maybeScheduleStop() {
|
private boolean isAnyRequestActive() {
|
||||||
if (!isAnyRequestActive()) {
|
return mIdToClientInfoMap.size() != 0;
|
||||||
cancelStop();
|
}
|
||||||
sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
|
|
||||||
|
private void scheduleStop() {
|
||||||
|
sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
|
||||||
|
}
|
||||||
|
private void maybeScheduleStop() {
|
||||||
|
// The native daemon should stay alive and can't be cleanup
|
||||||
|
// if any legacy client connected.
|
||||||
|
if (!isAnyRequestActive() && mLegacyClientCount == 0) {
|
||||||
|
scheduleStop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelStop() {
|
private void cancelStop() {
|
||||||
this.removeMessages(NsdManager.DAEMON_CLEANUP);
|
this.removeMessages(NsdManager.DAEMON_CLEANUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +174,16 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
|
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
cInfo = mClients.get(msg.replyTo);
|
cInfo = mClients.get(msg.replyTo);
|
||||||
if (cInfo != null) {
|
if (cInfo != null) {
|
||||||
cInfo.expungeAllRequests();
|
cInfo.expungeAllRequests();
|
||||||
mClients.remove(msg.replyTo);
|
mClients.remove(msg.replyTo);
|
||||||
|
if (cInfo.isLegacy()) {
|
||||||
|
mLegacyClientCount -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
maybeScheduleStop();
|
||||||
break;
|
break;
|
||||||
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
|
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
|
||||||
AsyncChannel ac = new AsyncChannel();
|
AsyncChannel ac = new AsyncChannel();
|
||||||
@@ -197,6 +212,17 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
case NsdManager.DAEMON_CLEANUP:
|
case NsdManager.DAEMON_CLEANUP:
|
||||||
mDaemon.maybeStop();
|
mDaemon.maybeStop();
|
||||||
break;
|
break;
|
||||||
|
// This event should be only sent by the legacy (target SDK < S) clients.
|
||||||
|
// Mark the sending client as legacy.
|
||||||
|
case NsdManager.DAEMON_STARTUP:
|
||||||
|
cInfo = mClients.get(msg.replyTo);
|
||||||
|
if (cInfo != null) {
|
||||||
|
cancelStop();
|
||||||
|
cInfo.setLegacy();
|
||||||
|
mLegacyClientCount += 1;
|
||||||
|
maybeStartDaemon();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NsdManager.NATIVE_DAEMON_EVENT:
|
case NsdManager.NATIVE_DAEMON_EVENT:
|
||||||
default:
|
default:
|
||||||
Slog.e(TAG, "Unhandled " + msg);
|
Slog.e(TAG, "Unhandled " + msg);
|
||||||
@@ -235,7 +261,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
public void exit() {
|
public void exit() {
|
||||||
// TODO: it is incorrect to stop the daemon without expunging all requests
|
// TODO: it is incorrect to stop the daemon without expunging all requests
|
||||||
// and sending error callbacks to clients.
|
// and sending error callbacks to clients.
|
||||||
maybeScheduleStop();
|
scheduleStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requestLimitReached(ClientInfo clientInfo) {
|
private boolean requestLimitReached(ClientInfo clientInfo) {
|
||||||
@@ -271,9 +297,6 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
return NOT_HANDLED;
|
return NOT_HANDLED;
|
||||||
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
|
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
|
||||||
return NOT_HANDLED;
|
return NOT_HANDLED;
|
||||||
}
|
|
||||||
|
|
||||||
switch (msg.what) {
|
|
||||||
case NsdManager.DISABLE:
|
case NsdManager.DISABLE:
|
||||||
//TODO: cleanup clients
|
//TODO: cleanup clients
|
||||||
transitionTo(mDisabledState);
|
transitionTo(mDisabledState);
|
||||||
@@ -531,10 +554,6 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAnyRequestActive() {
|
|
||||||
return mIdToClientInfoMap.size() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String unescape(String s) {
|
private String unescape(String s) {
|
||||||
StringBuilder sb = new StringBuilder(s.length());
|
StringBuilder sb = new StringBuilder(s.length());
|
||||||
for (int i = 0; i < s.length(); ++i) {
|
for (int i = 0; i < s.length(); ++i) {
|
||||||
@@ -859,6 +878,9 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
/* A map from client id to the type of the request we had received */
|
/* A map from client id to the type of the request we had received */
|
||||||
private final SparseIntArray mClientRequests = new SparseIntArray();
|
private final SparseIntArray mClientRequests = new SparseIntArray();
|
||||||
|
|
||||||
|
// The target SDK of this client < Build.VERSION_CODES.S
|
||||||
|
private boolean mIsLegacy = false;
|
||||||
|
|
||||||
private ClientInfo(AsyncChannel c, Messenger m) {
|
private ClientInfo(AsyncChannel c, Messenger m) {
|
||||||
mChannel = c;
|
mChannel = c;
|
||||||
mMessenger = m;
|
mMessenger = m;
|
||||||
@@ -871,6 +893,7 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
sb.append("mChannel ").append(mChannel).append("\n");
|
sb.append("mChannel ").append(mChannel).append("\n");
|
||||||
sb.append("mMessenger ").append(mMessenger).append("\n");
|
sb.append("mMessenger ").append(mMessenger).append("\n");
|
||||||
sb.append("mResolvedService ").append(mResolvedService).append("\n");
|
sb.append("mResolvedService ").append(mResolvedService).append("\n");
|
||||||
|
sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
|
||||||
for(int i = 0; i< mClientIds.size(); i++) {
|
for(int i = 0; i< mClientIds.size(); i++) {
|
||||||
int clientID = mClientIds.keyAt(i);
|
int clientID = mClientIds.keyAt(i);
|
||||||
sb.append("clientId ").append(clientID).
|
sb.append("clientId ").append(clientID).
|
||||||
@@ -880,6 +903,14 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLegacy() {
|
||||||
|
return mIsLegacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLegacy() {
|
||||||
|
mIsLegacy = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove any pending requests from the global map when we get rid of a client,
|
// Remove any pending requests from the global map when we get rid of a client,
|
||||||
// and send cancellations to the daemon.
|
// and send cancellations to the daemon.
|
||||||
private void expungeAllRequests() {
|
private void expungeAllRequests() {
|
||||||
@@ -907,7 +938,6 @@ public class NsdService extends INsdManager.Stub {
|
|||||||
}
|
}
|
||||||
mClientIds.clear();
|
mClientIds.clear();
|
||||||
mClientRequests.clear();
|
mClientRequests.clear();
|
||||||
mNsdStateMachine.maybeScheduleStop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
|
// mClientIds is a sparse array of listener id -> mDnsClient id. For a given mDnsClient id,
|
||||||
|
|||||||
@@ -289,8 +289,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
|||||||
private String mActiveIface;
|
private String mActiveIface;
|
||||||
|
|
||||||
/** Set of any ifaces associated with mobile networks since boot. */
|
/** Set of any ifaces associated with mobile networks since boot. */
|
||||||
@GuardedBy("mStatsLock")
|
private volatile String[] mMobileIfaces = new String[0];
|
||||||
private String[] mMobileIfaces = new String[0];
|
|
||||||
|
|
||||||
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
|
/** Set of all ifaces currently used by traffic that does not explicitly specify a Network. */
|
||||||
@GuardedBy("mStatsLock")
|
@GuardedBy("mStatsLock")
|
||||||
@@ -935,7 +934,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMobileIfaces() {
|
public String[] getMobileIfaces() {
|
||||||
return mMobileIfaces;
|
// TODO (b/192758557): Remove debug log.
|
||||||
|
if (ArrayUtils.contains(mMobileIfaces, null)) {
|
||||||
|
throw new NullPointerException(
|
||||||
|
"null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
|
||||||
|
}
|
||||||
|
return mMobileIfaces.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1084,7 +1088,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getIfaceStats(String iface, int type) {
|
public long getIfaceStats(@NonNull String iface, int type) {
|
||||||
|
Objects.requireNonNull(iface);
|
||||||
long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
|
long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
|
||||||
if (nativeIfaceStats == -1) {
|
if (nativeIfaceStats == -1) {
|
||||||
return nativeIfaceStats;
|
return nativeIfaceStats;
|
||||||
@@ -1382,7 +1387,12 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mMobileIfaces = mobileIfaces.toArray(new String[mobileIfaces.size()]);
|
mMobileIfaces = mobileIfaces.toArray(new String[0]);
|
||||||
|
// TODO (b/192758557): Remove debug log.
|
||||||
|
if (ArrayUtils.contains(mMobileIfaces, null)) {
|
||||||
|
throw new NullPointerException(
|
||||||
|
"null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
|
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
|
||||||
|
|||||||
Reference in New Issue
Block a user