Merge changes from topic "vpnmanager-events-T" into tm-dev
* changes: Address leftover comments of ag/18108411 Address leftover comments of ag/18112116 Rename vars of Ikev2VpnRunner Send VPN manager event when there is a network error Send VPN manager event when there is an IkeProtocolException
This commit is contained in:
committed by
Android (Google) Code Review
commit
839756e661
@@ -88,7 +88,10 @@ import android.net.ipsec.ike.IkeSession;
|
|||||||
import android.net.ipsec.ike.IkeSessionCallback;
|
import android.net.ipsec.ike.IkeSessionCallback;
|
||||||
import android.net.ipsec.ike.IkeSessionParams;
|
import android.net.ipsec.ike.IkeSessionParams;
|
||||||
import android.net.ipsec.ike.IkeTunnelConnectionParams;
|
import android.net.ipsec.ike.IkeTunnelConnectionParams;
|
||||||
|
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
|
||||||
|
import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
|
||||||
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
import android.net.ipsec.ike.exceptions.IkeProtocolException;
|
||||||
|
import android.net.ipsec.ike.exceptions.IkeTimeoutException;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build.VERSION_CODES;
|
import android.os.Build.VERSION_CODES;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -760,31 +763,36 @@ public class Vpn {
|
|||||||
// Also notify the new package if there was a provider change.
|
// Also notify the new package if there was a provider change.
|
||||||
final boolean shouldNotifyNewPkg = isVpnApp(packageName) && isPackageChanged;
|
final boolean shouldNotifyNewPkg = isVpnApp(packageName) && isPackageChanged;
|
||||||
|
|
||||||
if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
|
if (!setAlwaysOnPackageInternal(packageName, lockdown, lockdownAllowlist)) {
|
||||||
saveAlwaysOnPackage();
|
return false;
|
||||||
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
|
}
|
||||||
// ConnectivityServiceTest.
|
|
||||||
if (shouldNotifyOldPkg && SdkLevel.isAtLeastT()) {
|
saveAlwaysOnPackage();
|
||||||
// If both of shouldNotifyOldPkg & isPackageChanged are true, which means the
|
|
||||||
// always-on of old package is disabled or the old package is replaced with the new
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
|
||||||
// package. In this case, VpnProfileState should be disconnected.
|
// ConnectivityServiceTest.
|
||||||
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
|
if (!SdkLevel.isAtLeastT()) {
|
||||||
-1 /* errorClass */, -1 /* errorCode*/, oldPackage,
|
|
||||||
null /* sessionKey */, isPackageChanged ? makeDisconnectedVpnProfileState()
|
|
||||||
: makeVpnProfileStateLocked(),
|
|
||||||
null /* underlyingNetwork */, null /* nc */, null /* lp */);
|
|
||||||
}
|
|
||||||
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
|
|
||||||
// ConnectivityServiceTest.
|
|
||||||
if (shouldNotifyNewPkg && SdkLevel.isAtLeastT()) {
|
|
||||||
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
|
|
||||||
-1 /* errorClass */, -1 /* errorCode*/, packageName,
|
|
||||||
getSessionKeyLocked(), makeVpnProfileStateLocked(),
|
|
||||||
null /* underlyingNetwork */, null /* nc */, null /* lp */);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
if (shouldNotifyOldPkg) {
|
||||||
|
// If both of shouldNotifyOldPkg & isPackageChanged are true, that means the
|
||||||
|
// always-on of old package is disabled or the old package is replaced with the new
|
||||||
|
// package. In this case, VpnProfileState should be disconnected.
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
|
||||||
|
-1 /* errorClass */, -1 /* errorCode*/, oldPackage,
|
||||||
|
null /* sessionKey */, isPackageChanged ? makeDisconnectedVpnProfileState()
|
||||||
|
: makeVpnProfileStateLocked(),
|
||||||
|
null /* underlyingNetwork */, null /* nc */, null /* lp */);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldNotifyNewPkg) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED,
|
||||||
|
-1 /* errorClass */, -1 /* errorCode*/, packageName,
|
||||||
|
getSessionKeyLocked(), makeVpnProfileStateLocked(),
|
||||||
|
null /* underlyingNetwork */, null /* nc */, null /* lp */);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2530,6 +2538,21 @@ public class Vpn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected synchronized NetworkCapabilities getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
NetworkCapabilities nc) {
|
||||||
|
if (nc == null) return null;
|
||||||
|
return mConnectivityManager.getRedactedNetworkCapabilitiesForPackage(
|
||||||
|
nc, mOwnerUID, mPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected synchronized LinkProperties getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
LinkProperties lp) {
|
||||||
|
if (lp == null) return null;
|
||||||
|
return mConnectivityManager.getRedactedLinkPropertiesForPackage(lp, mOwnerUID, mPackage);
|
||||||
|
}
|
||||||
|
|
||||||
/** This class represents the common interface for all VPN runners. */
|
/** This class represents the common interface for all VPN runners. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
abstract class VpnRunner extends Thread {
|
abstract class VpnRunner extends Thread {
|
||||||
@@ -2564,6 +2587,10 @@ public class Vpn {
|
|||||||
interface IkeV2VpnRunnerCallback {
|
interface IkeV2VpnRunnerCallback {
|
||||||
void onDefaultNetworkChanged(@NonNull Network network);
|
void onDefaultNetworkChanged(@NonNull Network network);
|
||||||
|
|
||||||
|
void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc);
|
||||||
|
|
||||||
|
void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp);
|
||||||
|
|
||||||
void onChildOpened(
|
void onChildOpened(
|
||||||
@NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
|
@NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
|
||||||
|
|
||||||
@@ -2620,14 +2647,14 @@ public class Vpn {
|
|||||||
@Nullable private IpSecTunnelInterface mTunnelIface;
|
@Nullable private IpSecTunnelInterface mTunnelIface;
|
||||||
@Nullable private IkeSession mSession;
|
@Nullable private IkeSession mSession;
|
||||||
@Nullable private Network mActiveNetwork;
|
@Nullable private Network mActiveNetwork;
|
||||||
|
@Nullable private NetworkCapabilities mUnderlyingNetworkCapabilities;
|
||||||
|
@Nullable private LinkProperties mUnderlyingLinkProperties;
|
||||||
private final String mSessionKey;
|
private final String mSessionKey;
|
||||||
|
|
||||||
IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
|
IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
|
||||||
super(TAG);
|
super(TAG);
|
||||||
mProfile = profile;
|
mProfile = profile;
|
||||||
mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
|
mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
|
||||||
// Pass mExecutor into Ikev2VpnNetworkCallback and make sure that IkeV2VpnRunnerCallback
|
|
||||||
// will be called by the mExecutor thread.
|
|
||||||
mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
|
mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
|
||||||
mSessionKey = UUID.randomUUID().toString();
|
mSessionKey = UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
@@ -2849,6 +2876,16 @@ public class Vpn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Called when the NetworkCapabilities of underlying network is changed */
|
||||||
|
public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
|
||||||
|
mUnderlyingNetworkCapabilities = nc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the LinkProperties of underlying network is changed */
|
||||||
|
public void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp) {
|
||||||
|
mUnderlyingLinkProperties = lp;
|
||||||
|
}
|
||||||
|
|
||||||
/** Marks the state as FAILED, and disconnects. */
|
/** Marks the state as FAILED, and disconnects. */
|
||||||
private void markFailedAndDisconnect(Exception exception) {
|
private void markFailedAndDisconnect(Exception exception) {
|
||||||
synchronized (Vpn.this) {
|
synchronized (Vpn.this) {
|
||||||
@@ -2879,28 +2916,120 @@ public class Vpn {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception instanceof IkeProtocolException) {
|
synchronized (Vpn.this) {
|
||||||
final IkeProtocolException ikeException = (IkeProtocolException) exception;
|
if (exception instanceof IkeProtocolException) {
|
||||||
|
final IkeProtocolException ikeException = (IkeProtocolException) exception;
|
||||||
|
|
||||||
switch (ikeException.getErrorType()) {
|
switch (ikeException.getErrorType()) {
|
||||||
case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
|
case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
|
||||||
case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
|
case IkeProtocolException.ERROR_TYPE_INVALID_KE_PAYLOAD: // Fallthrough
|
||||||
case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
|
case IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED: // Fallthrough
|
||||||
case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
|
case IkeProtocolException.ERROR_TYPE_SINGLE_PAIR_REQUIRED: // Fallthrough
|
||||||
case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
|
case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
|
||||||
case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
|
case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
|
||||||
// All the above failures are configuration errors, and are terminal
|
// All the above failures are configuration errors, and are terminal
|
||||||
markFailedAndDisconnect(exception);
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
return;
|
// decoupled from ConnectivityServiceTest.
|
||||||
// All other cases possibly recoverable.
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
|
||||||
|
ikeException.getErrorType(),
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
markFailedAndDisconnect(exception);
|
||||||
|
return;
|
||||||
|
// All other cases possibly recoverable.
|
||||||
|
default:
|
||||||
|
// All the above failures are configuration errors, and are terminal
|
||||||
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
|
// decoupled from ConnectivityServiceTest.
|
||||||
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_RECOVERABLE,
|
||||||
|
ikeException.getErrorType(),
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (exception instanceof IllegalArgumentException) {
|
||||||
|
// Failed to build IKE/ChildSessionParams; fatal profile configuration error
|
||||||
|
markFailedAndDisconnect(exception);
|
||||||
|
return;
|
||||||
|
} else if (exception instanceof IkeNetworkLostException) {
|
||||||
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
|
// decoupled from ConnectivityServiceTest.
|
||||||
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_RECOVERABLE,
|
||||||
|
VpnManager.ERROR_CODE_NETWORK_LOST,
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
} else if (exception instanceof IkeNonProtocolException) {
|
||||||
|
if (exception.getCause() instanceof UnknownHostException) {
|
||||||
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
|
// decoupled from ConnectivityServiceTest.
|
||||||
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_RECOVERABLE,
|
||||||
|
VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
} else if (exception.getCause() instanceof IkeTimeoutException) {
|
||||||
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
|
// decoupled from ConnectivityServiceTest.
|
||||||
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_RECOVERABLE,
|
||||||
|
VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
} else if (exception.getCause() instanceof IOException) {
|
||||||
|
// TODO(b/230548427): Remove SDK check once VPN related stuff are
|
||||||
|
// decoupled from ConnectivityServiceTest.
|
||||||
|
if (SdkLevel.isAtLeastT()) {
|
||||||
|
sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
|
||||||
|
VpnManager.ERROR_CLASS_RECOVERABLE,
|
||||||
|
VpnManager.ERROR_CODE_NETWORK_IO,
|
||||||
|
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
|
||||||
|
mActiveNetwork,
|
||||||
|
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingNetworkCapabilities),
|
||||||
|
getRedactedLinkPropertiesOfUnderlyingNetwork(
|
||||||
|
mUnderlyingLinkProperties));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (exception != null) {
|
||||||
|
Log.wtf(TAG, "onSessionLost: exception = " + exception);
|
||||||
}
|
}
|
||||||
} else if (exception instanceof IllegalArgumentException) {
|
|
||||||
// Failed to build IKE/ChildSessionParams; fatal profile configuration error
|
|
||||||
markFailedAndDisconnect(exception);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mActiveNetwork = null;
|
mActiveNetwork = null;
|
||||||
|
mUnderlyingNetworkCapabilities = null;
|
||||||
|
mUnderlyingLinkProperties = null;
|
||||||
|
|
||||||
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
|
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
|
||||||
// (Mirrors VpnService behavior)
|
// (Mirrors VpnService behavior)
|
||||||
@@ -2965,6 +3094,8 @@ public class Vpn {
|
|||||||
*/
|
*/
|
||||||
private void disconnectVpnRunner() {
|
private void disconnectVpnRunner() {
|
||||||
mActiveNetwork = null;
|
mActiveNetwork = null;
|
||||||
|
mUnderlyingNetworkCapabilities = null;
|
||||||
|
mUnderlyingLinkProperties = null;
|
||||||
mIsRunning = false;
|
mIsRunning = false;
|
||||||
|
|
||||||
resetIkeState();
|
resetIkeState();
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ import android.net.InetAddresses;
|
|||||||
import android.net.IpPrefix;
|
import android.net.IpPrefix;
|
||||||
import android.net.IpSecAlgorithm;
|
import android.net.IpSecAlgorithm;
|
||||||
import android.net.IpSecTransform;
|
import android.net.IpSecTransform;
|
||||||
|
import android.net.LinkProperties;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
|
import android.net.NetworkCapabilities;
|
||||||
import android.net.RouteInfo;
|
import android.net.RouteInfo;
|
||||||
import android.net.eap.EapSessionConfig;
|
import android.net.eap.EapSessionConfig;
|
||||||
import android.net.ipsec.ike.ChildSaProposal;
|
import android.net.ipsec.ike.ChildSaProposal;
|
||||||
@@ -86,7 +88,7 @@ import java.util.Arrays;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class to build and convert IKEv2/IPsec parameters.
|
* Utility class to build and convert IKEv2/IPsec parameters.
|
||||||
@@ -377,10 +379,10 @@ public class VpnIkev2Utils {
|
|||||||
static class Ikev2VpnNetworkCallback extends NetworkCallback {
|
static class Ikev2VpnNetworkCallback extends NetworkCallback {
|
||||||
private final String mTag;
|
private final String mTag;
|
||||||
private final Vpn.IkeV2VpnRunnerCallback mCallback;
|
private final Vpn.IkeV2VpnRunnerCallback mCallback;
|
||||||
private final ExecutorService mExecutor;
|
private final Executor mExecutor;
|
||||||
|
|
||||||
Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback,
|
Ikev2VpnNetworkCallback(String tag, Vpn.IkeV2VpnRunnerCallback callback,
|
||||||
ExecutorService executor) {
|
Executor executor) {
|
||||||
mTag = tag;
|
mTag = tag;
|
||||||
mCallback = callback;
|
mCallback = callback;
|
||||||
mExecutor = executor;
|
mExecutor = executor;
|
||||||
@@ -392,6 +394,22 @@ public class VpnIkev2Utils {
|
|||||||
mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
|
mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCapabilitiesChanged(@NonNull Network network,
|
||||||
|
@NonNull NetworkCapabilities networkCapabilities) {
|
||||||
|
Log.d(mTag, "NC changed for net " + network + " : " + networkCapabilities);
|
||||||
|
mExecutor.execute(
|
||||||
|
() -> mCallback.onDefaultNetworkCapabilitiesChanged(networkCapabilities));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLinkPropertiesChanged(@NonNull Network network,
|
||||||
|
@NonNull LinkProperties linkProperties) {
|
||||||
|
Log.d(mTag, "LP changed for net " + network + " : " + linkProperties);
|
||||||
|
mExecutor.execute(
|
||||||
|
() -> mCallback.onDefaultNetworkLinkPropertiesChanged(linkProperties));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLost(@NonNull Network network) {
|
public void onLost(@NonNull Network network) {
|
||||||
Log.d(mTag, "Tearing down; lost network: " + network);
|
Log.d(mTag, "Tearing down; lost network: " + network);
|
||||||
|
|||||||
Reference in New Issue
Block a user