From de31d856fc978e828879d39e2f4cab37e352e0e5 Mon Sep 17 00:00:00 2001 From: markchien Date: Mon, 2 Mar 2020 23:27:48 +0800 Subject: [PATCH] Send offload status changed callback The callback would be fired when offload started, stopped, or failed. If offload is not supported, "failed" callback would be fired when user enable tethering. Enabling multiple tethering would not have multiple offload status callbacks because offload should already be started or failed. Bug: 130596697 Test: -build, flash, boot -atest TetheringTests -ON/OFF hotspot Change-Id: Ia0398601144b0e5f61dc0c5771eacf13e7cfbb59 --- api/module-lib-current.txt | 4 + api/system-current.txt | 4 + api/test-current.txt | 4 + .../android/net/ITetheringEventCallback.aidl | 1 + .../net/TetheringCallbackStartedParcel.aidl | 3 +- .../src/android/net/TetheringManager.java | 37 +++++++++ .../connectivity/tethering/Tethering.java | 32 +++++++- .../connectivity/tethering/TetheringTest.java | 75 +++++++++++++++++++ 8 files changed, 158 insertions(+), 2 deletions(-) diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 1b72fc3db058a..89b83e63ee16e 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -86,6 +86,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -102,6 +105,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List); method public void onTetheredInterfacesChanged(@NonNull java.util.List); diff --git a/api/system-current.txt b/api/system-current.txt index 764a386e45ef8..b179a0c437801 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4914,6 +4914,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -4930,6 +4933,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List); method public void onTetheredInterfacesChanged(@NonNull java.util.List); diff --git a/api/test-current.txt b/api/test-current.txt index c2553ed35ef70..28e43d86f6656 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1622,6 +1622,9 @@ package android.net { field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 + field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 + field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 + field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 } public static interface TetheringManager.OnTetheringEntitlementResultListener { @@ -1638,6 +1641,7 @@ package android.net { ctor public TetheringManager.TetheringEventCallback(); method public void onClientsChanged(@NonNull java.util.Collection); method public void onError(@NonNull String, int); + method public void onOffloadStatusChanged(int); method @Deprecated public void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); method public void onTetherableInterfacesChanged(@NonNull java.util.List); method public void onTetheredInterfacesChanged(@NonNull java.util.List); diff --git a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl index a55419383380e..b4e3ba46791c6 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl @@ -35,4 +35,5 @@ oneway interface ITetheringEventCallback void onConfigurationChanged(in TetheringConfigurationParcel config); void onTetherStatesChanged(in TetherStatesParcel states); void onTetherClientsChanged(in List clients); + void onOffloadStatusChanged(int status); } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl index c064aa4d9a61c..253eacbd23e79 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl @@ -31,4 +31,5 @@ parcelable TetheringCallbackStartedParcel { TetheringConfigurationParcel config; TetherStatesParcel states; List tetheredClients; -} \ No newline at end of file + int offloadStatus; +} diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index bfa962a18c9a4..231de8bc98aa8 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -18,6 +18,7 @@ package android.net; 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 android.util.Log; 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 class TetheringManager { 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 @@ public class TetheringManager { @Override public void onTetherClientsChanged(List clients) { } + @Override + public void onOffloadStatusChanged(int status) { } + public void waitForStarted() { mWaitForCallback.block(DEFAULT_TIMEOUT_MS); throwIfPermissionFailure(mError); @@ -802,6 +825,14 @@ public class TetheringManager { * @param clients The new set of tethered clients; the collection is not ordered. */ public void onClientsChanged(@NonNull Collection clients) {} + + /** + * Called when tethering offload status changes. + * + *

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 @@ public class TetheringManager { maybeSendTetherableIfacesChangedCallback(parcel.states); maybeSendTetheredIfacesChangedCallback(parcel.states); callback.onClientsChanged(parcel.tetheredClients); + callback.onOffloadStatusChanged(parcel.offloadStatus); }); } @@ -960,6 +992,11 @@ public class TetheringManager { public void onTetherClientsChanged(final List 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); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 0958e8a1a407c..68650f7e7b389 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -44,6 +44,9 @@ import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; 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; @@ -237,6 +240,7 @@ public class Tethering { private TetherStatesParcel mTetherStatesParcel; private boolean mDataSaverEnabled = false; private String mWifiP2pTetherInterface = null; + private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED; @GuardedBy("mPublicSync") private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest; @@ -1901,12 +1905,15 @@ public class Tethering { // 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) { @@ -1967,6 +1974,13 @@ public class Tethering { mOffloadController.setLocalPrefixes(localPrefixes); } + + private void updateOffloadStatus(final int newStatus) { + if (newStatus == mOffloadStatus) return; + + mOffloadStatus = newStatus; + reportOffloadStatusChanged(mOffloadStatus); + } } } @@ -2001,6 +2015,7 @@ public class Tethering { parcel.tetheredClients = hasListPermission ? mConnectedClientsTracker.getLastTetheredClients() : Collections.emptyList(); + parcel.offloadStatus = mOffloadStatus; try { callback.onCallbackStarted(parcel); } catch (RemoteException e) { @@ -2095,6 +2110,21 @@ public class Tethering { } } + 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/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 4581b56f13dcb..1998cb3aa79cc 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -34,6 +34,9 @@ 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; @@ -169,6 +172,7 @@ public class TetheringTest { @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; @@ -463,6 +467,9 @@ public class TetheringTest { 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); @@ -1136,6 +1143,7 @@ public class TetheringTest { private final ArrayList mTetheringConfigs = new ArrayList<>(); private final ArrayList mTetherStates = new ArrayList<>(); + private final ArrayList 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. @@ -1171,6 +1179,11 @@ public class TetheringTest { assertNoConfigChangeCallback(); } + public void expectOffloadStatusChanged(final int expectedStatus) { + assertOffloadStatusChangedCallback(); + assertEquals(mOffloadStatus.remove(0), new Integer(expectedStatus)); + } + public TetherStatesParcel pollTetherStatesChanged() { assertStateChangeCallback(); return mTetherStates.remove(0); @@ -1196,11 +1209,17 @@ public class TetheringTest { // TODO: check this } + @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 @@ -1222,6 +1241,10 @@ public class TetheringTest { assertFalse(mTetherStates.isEmpty()); } + public void assertOffloadStatusChangedCallback() { + assertFalse(mOffloadStatus.isEmpty()); + } + public void assertNoCallback() { assertNoUpstreamChangeCallback(); assertNoConfigChangeCallback(); @@ -1270,6 +1293,7 @@ public class TetheringTest { 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); @@ -1287,6 +1311,7 @@ public class TetheringTest { 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); @@ -1296,6 +1321,7 @@ public class TetheringTest { 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); @@ -1307,9 +1333,58 @@ public class TetheringTest { 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();