Merge "[SP19] Reduce log when set limit and request stats update" into rvc-dev
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
index a554193..b4e3ba4 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl
@@ -35,4 +35,5 @@
void onConfigurationChanged(in TetheringConfigurationParcel config);
void onTetherStatesChanged(in TetherStatesParcel states);
void onTetherClientsChanged(in List<TetheredClient> clients);
+ void onOffloadStatusChanged(int status);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
index c064aa4..253eacb 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl
@@ -31,4 +31,5 @@
TetheringConfigurationParcel config;
TetherStatesParcel states;
List<TetheredClient> tetheredClients;
-}
\ No newline at end of file
+ int offloadStatus;
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index fd9f713..7f831ce 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -18,6 +18,7 @@
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -34,6 +35,8 @@
import com.android.internal.annotations.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -172,6 +175,23 @@
public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14;
public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, value = {
+ TETHER_HARDWARE_OFFLOAD_STOPPED,
+ TETHER_HARDWARE_OFFLOAD_STARTED,
+ TETHER_HARDWARE_OFFLOAD_FAILED,
+ })
+ public @interface TetherOffloadStatus {
+ }
+
+ /** Tethering offload status is stopped. */
+ public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0;
+ /** Tethering offload status is started. */
+ public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1;
+ /** Fail to start tethering offload. */
+ public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2;
+
/**
* Create a TetheringManager object for interacting with the tethering service.
*
@@ -378,6 +398,9 @@
@Override
public void onTetherClientsChanged(List<TetheredClient> clients) { }
+ @Override
+ public void onOffloadStatusChanged(int status) { }
+
public void waitForStarted() {
mWaitForCallback.block(DEFAULT_TIMEOUT_MS);
throwIfPermissionFailure(mError);
@@ -802,6 +825,14 @@
* @param clients The new set of tethered clients; the collection is not ordered.
*/
public void onClientsChanged(@NonNull Collection<TetheredClient> clients) {}
+
+ /**
+ * Called when tethering offload status changes.
+ *
+ * <p>This will be called immediately after the callback is registered.
+ * @param status The offload status.
+ */
+ public void onOffloadStatusChanged(@TetherOffloadStatus int status) {}
}
/**
@@ -925,6 +956,7 @@
maybeSendTetherableIfacesChangedCallback(parcel.states);
maybeSendTetheredIfacesChangedCallback(parcel.states);
callback.onClientsChanged(parcel.tetheredClients);
+ callback.onOffloadStatusChanged(parcel.offloadStatus);
});
}
@@ -960,6 +992,11 @@
public void onTetherClientsChanged(final List<TetheredClient> clients) {
executor.execute(() -> callback.onClientsChanged(clients));
}
+
+ @Override
+ public void onOffloadStatusChanged(final int status) {
+ executor.execute(() -> callback.onOffloadStatusChanged(status));
+ }
};
getConnector(c -> c.registerTetheringEventCallback(remoteCallback, callerPkg));
mTetheringEventCallbacks.put(callback, remoteCallback);
@@ -1131,6 +1168,25 @@
public boolean isTetheringSupported() {
final String callerPkg = mContext.getOpPackageName();
+ return isTetheringSupported(callerPkg);
+ }
+
+ /**
+ * Check if the device allows for tethering. It may be disabled via {@code ro.tether.denied}
+ * system property, Settings.TETHER_SUPPORTED or due to device configuration. This is useful
+ * for system components that query this API on behalf of an app. In particular, Bluetooth
+ * has @UnsupportedAppUsage calls that will let apps turn on bluetooth tethering if they have
+ * the right permissions, but such an app needs to know whether it can (permissions as well
+ * as support from the device) turn on tethering in the first place to show the appropriate UI.
+ *
+ * @param callerPkg The caller package name, if it is not matching the calling uid,
+ * SecurityException would be thrown.
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public boolean isTetheringSupported(@NonNull final String callerPkg) {
+
final RequestDispatcher dispatcher = new RequestDispatcher();
final int ret = dispatcher.waitForResult((connector, listener) -> {
try {
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 3acc766..38f8609 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -810,7 +810,7 @@
rule.dstMac.toByteArray());
mIpv6ForwardingRules.put(rule.address, rule);
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Could not add IPv6 downstream rule: " + e);
+ mLog.e("Could not add IPv6 downstream rule: ", e);
}
}
@@ -821,10 +821,17 @@
mIpv6ForwardingRules.remove(rule.address);
}
} catch (RemoteException | ServiceSpecificException e) {
- Log.e(TAG, "Could not remove IPv6 downstream rule: " + e);
+ mLog.e("Could not remove IPv6 downstream rule: ", e);
}
}
+ private void clearIpv6ForwardingRules() {
+ for (Ipv6ForwardingRule rule : mIpv6ForwardingRules.values()) {
+ removeIpv6ForwardingRule(rule, false /*removeFromMap*/);
+ }
+ mIpv6ForwardingRules.clear();
+ }
+
// Convenience method to replace a rule with the same rule on a new upstream interface.
// Allows replacing the rules in one iteration pass without ConcurrentModificationExceptions.
// Relies on the fact that rules are in a map indexed by IP address.
@@ -837,6 +844,12 @@
// changes or if a neighbor event is received.
private void updateIpv6ForwardingRules(int prevUpstreamIfindex, int upstreamIfindex,
NeighborEvent e) {
+ // If we no longer have an upstream, clear forwarding rules and do nothing else.
+ if (upstreamIfindex == 0) {
+ clearIpv6ForwardingRules();
+ return;
+ }
+
// If the upstream interface has changed, remove all rules and re-add them with the new
// upstream interface.
if (prevUpstreamIfindex != upstreamIfindex) {
@@ -846,13 +859,14 @@
}
// If we're here to process a NeighborEvent, do so now.
+ // mInterfaceParams must be non-null or the event would not have arrived.
if (e == null) return;
if (!(e.ip instanceof Inet6Address) || e.ip.isMulticastAddress()
|| e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) {
return;
}
- Ipv6ForwardingRule rule = new Ipv6ForwardingRule(mLastIPv6UpstreamIfindex,
+ Ipv6ForwardingRule rule = new Ipv6ForwardingRule(upstreamIfindex,
mInterfaceParams.index, (Inet6Address) e.ip, mInterfaceParams.macAddr,
e.macAddr);
if (e.isValid()) {
@@ -1095,6 +1109,7 @@
for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
mUpstreamIfaceSet = null;
+ clearIpv6ForwardingRules();
}
private void cleanupUpstreamInterface(String upstreamIface) {
diff --git a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index ca74430..f89da84 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -44,6 +44,9 @@
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
import static android.net.util.TetheringMessageBase.BASE_MASTER;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -220,6 +223,7 @@
private final UserRestrictionActionListener mTetheringRestriction;
private final ActiveDataSubIdListener mActiveDataSubIdListener;
private final ConnectedClientsTracker mConnectedClientsTracker;
+ private final TetheringThreadExecutor mExecutor;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
private ITetheringEventCallback mTetheringEventCallback = null;
@@ -236,6 +240,7 @@
private TetherStatesParcel mTetherStatesParcel;
private boolean mDataSaverEnabled = false;
private String mWifiP2pTetherInterface = null;
+ private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
@GuardedBy("mPublicSync")
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
@@ -296,8 +301,8 @@
final UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
- final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
- mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
+ mExecutor = new TetheringThreadExecutor(mHandler);
+ mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor);
// Load tethering configuration.
updateConfiguration();
@@ -315,9 +320,7 @@
final WifiManager wifiManager = getWifiManager();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(
- mHandler::post /* executor */,
- new TetheringSoftApCallback());
+ wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
}
}
@@ -606,14 +609,17 @@
Context.ETHERNET_SERVICE);
synchronized (mPublicSync) {
if (enable) {
+ if (mEthernetCallback != null) return TETHER_ERROR_NO_ERROR;
+
mEthernetCallback = new EthernetCallback();
- mEthernetIfaceRequest = em.requestTetheredInterface(mEthernetCallback);
+ mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback);
} else {
- if (mConfiguredEthernetIface != null) {
- stopEthernetTetheringLocked();
+ stopEthernetTetheringLocked();
+ if (mEthernetCallback != null) {
mEthernetIfaceRequest.release();
+ mEthernetCallback = null;
+ mEthernetIfaceRequest = null;
}
- mEthernetCallback = null;
}
}
return TETHER_ERROR_NO_ERROR;
@@ -1899,12 +1905,15 @@
// OffloadController implementation.
class OffloadWrapper {
public void start() {
- mOffloadController.start();
+ final int status = mOffloadController.start() ? TETHER_HARDWARE_OFFLOAD_STARTED
+ : TETHER_HARDWARE_OFFLOAD_FAILED;
+ updateOffloadStatus(status);
sendOffloadExemptPrefixes();
}
public void stop() {
mOffloadController.stop();
+ updateOffloadStatus(TETHER_HARDWARE_OFFLOAD_STOPPED);
}
public void updateUpstreamNetworkState(UpstreamNetworkState ns) {
@@ -1965,6 +1974,13 @@
mOffloadController.setLocalPrefixes(localPrefixes);
}
+
+ private void updateOffloadStatus(final int newStatus) {
+ if (newStatus == mOffloadStatus) return;
+
+ mOffloadStatus = newStatus;
+ reportOffloadStatusChanged(mOffloadStatus);
+ }
}
}
@@ -1999,6 +2015,7 @@
parcel.tetheredClients = hasListPermission
? mConnectedClientsTracker.getLastTetheredClients()
: Collections.emptyList();
+ parcel.offloadStatus = mOffloadStatus;
try {
callback.onCallbackStarted(parcel);
} catch (RemoteException e) {
@@ -2093,6 +2110,21 @@
}
}
+ private void reportOffloadStatusChanged(final int status) {
+ final int length = mTetheringEventCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ try {
+ mTetheringEventCallbacks.getBroadcastItem(i).onOffloadStatusChanged(status);
+ } catch (RemoteException e) {
+ // Not really very much to do here.
+ }
+ }
+ } finally {
+ mTetheringEventCallbacks.finishBroadcast();
+ }
+ }
+
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 33b3558..948266d 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -43,6 +43,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
@@ -546,9 +547,9 @@
reset(mNetd);
// Link-local and multicast neighbors are ignored.
- recvNewNeigh(notMyIfindex, neighLL, NUD_REACHABLE, macA);
+ recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
verifyNoMoreInteractions(mNetd);
- recvNewNeigh(notMyIfindex, neighMC, NUD_REACHABLE, macA);
+ recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
verifyNoMoreInteractions(mNetd);
// A neighbor that is no longer valid causes the rule to be removed.
@@ -578,6 +579,52 @@
eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
inOrder.verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX),
eq(neighB.getAddress()));
+ reset(mNetd);
+
+ // When the upstream is lost, rules are removed.
+ dispatchTetherConnectionChanged(null, null);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+ eq(neighA.getAddress()));
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX2),
+ eq(neighB.getAddress()));
+ reset(mNetd);
+
+ // If the upstream is IPv4-only, no rules are added.
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+ reset(mNetd);
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ verifyNoMoreInteractions(mNetd);
+
+ // Rules can be added again once upstream IPv6 connectivity is available.
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ verify(mNetd, never()).tetherRuleAddDownstreamIpv6(anyInt(), anyInt(),
+ eq(neighA.getAddress()), any(), any());
+
+ // If upstream IPv6 connectivity is lost, rules are removed.
+ reset(mNetd);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, null);
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+
+ // When the interface goes down, rules are removed.
+ lp.setInterfaceName(UPSTREAM_IFACE);
+ dispatchTetherConnectionChanged(UPSTREAM_IFACE, lp);
+ recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
+ recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighA.getAddress()), eq(myMac.toByteArray()), eq(macA.toByteArray()));
+ verify(mNetd).tetherRuleAddDownstreamIpv6(eq(myIfindex), eq(UPSTREAM_IFINDEX),
+ eq(neighB.getAddress()), eq(myMac.toByteArray()), eq(macB.toByteArray()));
+ reset(mNetd);
+
+ mIpServer.stop();
+ mLooper.dispatchAll();
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighA.getAddress()));
+ verify(mNetd).tetherRuleRemoveDownstreamIpv6(eq(UPSTREAM_IFINDEX), eq(neighB.getAddress()));
+ reset(mNetd);
}
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
@@ -624,16 +671,15 @@
* @param v6lp IPv6 LinkProperties of the upstream interface, or null for an IPv4-only upstream.
*/
private void dispatchTetherConnectionChanged(String upstreamIface, LinkProperties v6lp) {
- mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED,
- new InterfaceSet(upstreamIface));
- if (v6lp != null) {
- mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, v6lp);
- }
+ dispatchTetherConnectionChanged(upstreamIface);
+ mIpServer.sendMessage(IpServer.CMD_IPV6_TETHER_UPDATE, v6lp);
mLooper.dispatchAll();
}
private void dispatchTetherConnectionChanged(String upstreamIface) {
- dispatchTetherConnectionChanged(upstreamIface, null);
+ final InterfaceSet ifs = (upstreamIface != null) ? new InterfaceSet(upstreamIface) : null;
+ mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED, ifs);
+ mLooper.dispatchAll();
}
private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) {
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index f2074bd..af7ad66 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -28,11 +28,15 @@
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -75,6 +79,8 @@
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
+import android.net.EthernetManager;
+import android.net.EthernetManager.TetheredInterfaceRequest;
import android.net.INetd;
import android.net.ITetheringEventCallback;
import android.net.InetAddresses;
@@ -166,6 +172,7 @@
@Mock private Context mContext;
@Mock private NetworkStatsManager mStatsManager;
@Mock private OffloadHardwareInterface mOffloadHardwareInterface;
+ @Mock private OffloadHardwareInterface.ForwardedStats mForwardedStats;
@Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@Mock private UsbManager mUsbManager;
@@ -180,6 +187,7 @@
@Mock private UserManager mUserManager;
@Mock private NetworkRequest mNetworkRequest;
@Mock private ConnectivityManager mCm;
+ @Mock private EthernetManager mEm;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -232,6 +240,7 @@
if (Context.USER_SERVICE.equals(name)) return mUserManager;
if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
+ if (Context.ETHERNET_SERVICE.equals(name)) return mEm;
return super.getSystemService(name);
}
@@ -458,6 +467,9 @@
mInterfaceConfiguration.flags = new String[0];
when(mRouterAdvertisementDaemon.start())
.thenReturn(true);
+ initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */,
+ 0 /* defaultDisabled */);
+ when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
mServiceContext = new TestContext(mContext);
when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
@@ -1131,6 +1143,7 @@
private final ArrayList<TetheringConfigurationParcel> mTetheringConfigs =
new ArrayList<>();
private final ArrayList<TetherStatesParcel> mTetherStates = new ArrayList<>();
+ private final ArrayList<Integer> mOffloadStatus = new ArrayList<>();
// This function will remove the recorded callbacks, so it must be called once for
// each callback. If this is called after multiple callback, the order matters.
@@ -1166,6 +1179,11 @@
assertNoConfigChangeCallback();
}
+ public void expectOffloadStatusChanged(final int expectedStatus) {
+ assertOffloadStatusChangedCallback();
+ assertEquals(mOffloadStatus.remove(0), new Integer(expectedStatus));
+ }
+
public TetherStatesParcel pollTetherStatesChanged() {
assertStateChangeCallback();
return mTetherStates.remove(0);
@@ -1192,10 +1210,16 @@
}
@Override
+ public void onOffloadStatusChanged(final int status) {
+ mOffloadStatus.add(status);
+ }
+
+ @Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
mActualUpstreams.add(parcel.upstreamNetwork);
mTetheringConfigs.add(parcel.config);
mTetherStates.add(parcel.states);
+ mOffloadStatus.add(parcel.offloadStatus);
}
@Override
@@ -1217,6 +1241,10 @@
assertFalse(mTetherStates.isEmpty());
}
+ public void assertOffloadStatusChangedCallback() {
+ assertFalse(mOffloadStatus.isEmpty());
+ }
+
public void assertNoCallback() {
assertNoUpstreamChangeCallback();
assertNoConfigChangeCallback();
@@ -1265,6 +1293,7 @@
mTethering.getTetheringConfiguration().toStableParcelable());
TetherStatesParcel tetherState = callback.pollTetherStatesChanged();
assertTetherStatesNotNullButEmpty(tetherState);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
// 2. Enable wifi tethering.
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
@@ -1282,6 +1311,7 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
callback.expectUpstreamChanged(upstreamState.network);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
// 3. Register second callback.
mTethering.registerTetheringEventCallback(callback2);
@@ -1291,6 +1321,7 @@
mTethering.getTetheringConfiguration().toStableParcelable());
tetherState = callback2.pollTetherStatesChanged();
assertEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+ callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
// 4. Unregister first callback and disable wifi tethering
mTethering.unregisterTetheringEventCallback(callback);
@@ -1302,10 +1333,59 @@
assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
mLooper.dispatchAll();
callback2.expectUpstreamChanged(new Network[] {null});
+ callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
callback.assertNoCallback();
}
@Test
+ public void testReportFailCallbackIfOffloadNotSupported() throws Exception {
+ final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+
+ // 1. Offload fail if no OffloadConfig.
+ initOffloadConfiguration(false /* offloadConfig */, true /* offloadControl */,
+ 0 /* defaultDisabled */);
+ runUsbTethering(upstreamState);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
+ runStopUSBTethering();
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ reset(mUsbManager);
+ // 2. Offload fail if no OffloadControl.
+ initOffloadConfiguration(true /* offloadConfig */, false /* offloadControl */,
+ 0 /* defaultDisabled */);
+ runUsbTethering(upstreamState);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
+ runStopUSBTethering();
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ reset(mUsbManager);
+ // 3. Offload fail if disabled by settings.
+ initOffloadConfiguration(true /* offloadConfig */, true /* offloadControl */,
+ 1 /* defaultDisabled */);
+ runUsbTethering(upstreamState);
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_FAILED);
+ runStopUSBTethering();
+ callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+ }
+
+ private void runStopUSBTethering() {
+ mTethering.stopTethering(TETHERING_USB);
+ mLooper.dispatchAll();
+ mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mLooper.dispatchAll();
+ }
+
+ private void initOffloadConfiguration(final boolean offloadConfig,
+ final boolean offloadControl, final int defaultDisabled) {
+ when(mOffloadHardwareInterface.initOffloadConfig()).thenReturn(offloadConfig);
+ when(mOffloadHardwareInterface.initOffloadControl(any())).thenReturn(offloadControl);
+ when(mOffloadHardwareInterface.getDefaultTetherOffloadDisabled()).thenReturn(
+ defaultDisabled);
+ }
+
+ @Test
public void testMultiSimAware() throws Exception {
final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId);
@@ -1316,6 +1396,24 @@
assertEquals(fakeSubId, newConfig.activeDataSubId);
}
+ @Test
+ public void testNoDuplicatedEthernetRequest() throws Exception {
+ final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
+ when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
+ mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null);
+ mLooper.dispatchAll();
+ verify(mEm, times(1)).requestTetheredInterface(any(), any());
+ mTethering.startTethering(createTetheringRquestParcel(TETHERING_ETHERNET), null);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mEm);
+ mTethering.stopTethering(TETHERING_ETHERNET);
+ mLooper.dispatchAll();
+ verify(mockRequest, times(1)).release();
+ mTethering.stopTethering(TETHERING_ETHERNET);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mEm);
+ }
+
private void workingWifiP2pGroupOwner(
boolean emulateInterfaceStatusChanged) throws Exception {
if (emulateInterfaceStatusChanged) {