diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl index b4e3ba4679..836761f864 100644 --- a/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl +++ b/Tethering/common/TetheringLib/src/android/net/ITetheringEventCallback.aidl @@ -36,4 +36,5 @@ oneway interface ITetheringEventCallback void onTetherStatesChanged(in TetherStatesParcel states); void onTetherClientsChanged(in List clients); void onOffloadStatusChanged(int status); + void onSupportedTetheringTypes(long supportedBitmap); } diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl index 253eacbd23..f33f846347 100644 --- a/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl +++ b/Tethering/common/TetheringLib/src/android/net/TetheringCallbackStartedParcel.aidl @@ -26,7 +26,7 @@ import android.net.TetherStatesParcel; * @hide */ parcelable TetheringCallbackStartedParcel { - boolean tetheringSupported; + long supportedTypes; Network upstreamNetwork; TetheringConfigurationParcel config; TetherStatesParcel states; diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 6f9b33e96a..b3f0cf2715 100644 --- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -183,6 +183,12 @@ public class TetheringManager { */ public static final int TETHERING_WIGIG = 6; + /** + * The int value of last tethering type. + * @hide + */ + public static final int MAX_TETHERING_TYPE = TETHERING_WIGIG; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = { @@ -519,6 +525,9 @@ public class TetheringManager { } } + @Override + public void onSupportedTetheringTypes(long supportedBitmap) { } + @Override public void onUpstreamChanged(Network network) { } @@ -1030,6 +1039,19 @@ public class TetheringManager { * upstream status. */ public interface TetheringEventCallback { + /** + * Called when tethering supported status changed. + * + *

This callback will be called immediately after the callback is + * registered, and never be called if there is changes afterward. + * + *

Tethering may be disabled via system properties, device configuration, or device + * policy restrictions. + * + * @param supported whether any tethering type is supported. + */ + default void onTetheringSupported(boolean supported) {} + /** * Called when tethering supported status changed. * @@ -1039,9 +1061,10 @@ public class TetheringManager { *

Tethering may be disabled via system properties, device configuration, or device * policy restrictions. * - * @param supported The new supported status + * @param supportedTypes a set of @TetheringType which is supported. + * @hide */ - default void onTetheringSupported(boolean supported) {} + default void onSupportedTetheringTypes(@NonNull Set supportedTypes) {} /** * Called when tethering upstream changed. @@ -1339,7 +1362,8 @@ public class TetheringManager { @Override public void onCallbackStarted(TetheringCallbackStartedParcel parcel) { executor.execute(() -> { - callback.onTetheringSupported(parcel.tetheringSupported); + callback.onSupportedTetheringTypes(unpackBits(parcel.supportedTypes)); + callback.onTetheringSupported(parcel.supportedTypes != 0); callback.onUpstreamChanged(parcel.upstreamNetwork); sendErrorCallbacks(parcel.states); sendRegexpsChanged(parcel.config); @@ -1358,6 +1382,13 @@ public class TetheringManager { }); } + @Override + public void onSupportedTetheringTypes(long supportedBitmap) { + executor.execute(() -> { + callback.onSupportedTetheringTypes(unpackBits(supportedBitmap)); + }); + } + private void sendRegexpsChanged(TetheringConfigurationParcel parcel) { callback.onTetherableInterfaceRegexpsChanged(new TetheringInterfaceRegexps( parcel.tetherableBluetoothRegexs, @@ -1395,6 +1426,23 @@ public class TetheringManager { } } + /** + * Unpack bitmap to a set of bit position intergers. + * @hide + */ + public static ArraySet unpackBits(long val) { + final ArraySet result = new ArraySet<>(Long.bitCount(val)); + int bitPos = 0; + while (val != 0) { + if ((val & 1) == 1) result.add(bitPos); + + val = val >>> 1; + bitPos++; + } + + return result; + } + /** * Remove tethering event callback previously registered with * {@link #registerTetheringEventCallback}. diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index 89ed6201f0..0c59b61f2c 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -281,6 +281,11 @@ public class Tethering { private BluetoothPan mBluetoothPan; private PanServiceListener mBluetoothPanListener; private ArrayList> mPendingPanRequests; + // AIDL doesn't support Set. Maintain a int bitmap here. When the bitmap is passed to + // TetheringManager, TetheringManager would convert it to a set of Integer types. + // mSupportedTypeBitmap should always be updated inside tethering internal thread but it may be + // read from binder thread which called TetheringService directly. + private volatile long mSupportedTypeBitmap; public Tethering(TetheringDependencies deps) { mLog.mark("Tethering.constructed"); @@ -512,6 +517,8 @@ public class Tethering { mUpstreamNetworkMonitor.setUpstreamConfig(mConfig.chooseUpstreamAutomatically, mConfig.isDunRequired); reportConfigurationChanged(mConfig.toStableParcelable()); + + updateSupportedDownstreams(mConfig); } private void maybeDunSettingChanged() { @@ -1550,26 +1557,6 @@ public class Tethering { return mConfig; } - boolean hasAnySupportedDownstream() { - if ((mConfig.tetherableUsbRegexs.length != 0) - || (mConfig.tetherableWifiRegexs.length != 0) - || (mConfig.tetherableBluetoothRegexs.length != 0)) { - return true; - } - - // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are - // disabled (whole tethering settings would be hidden). This means tethering would also not - // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are - // some devices in the field rely on this to disable tethering entirely. - if (!SdkLevel.isAtLeastT()) return false; - - return (mConfig.tetherableWifiP2pRegexs.length != 0) - || (mConfig.tetherableNcmRegexs.length != 0) - || isEthernetSupported(); - } - - // TODO: using EtherentManager new API to check whether ethernet is supported when the API is - // ready to use. private boolean isEthernetSupported() { return mContext.getSystemService(Context.ETHERNET_SERVICE) != null; } @@ -2359,7 +2346,7 @@ public class Tethering { mHandler.post(() -> { mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission)); final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel(); - parcel.tetheringSupported = isTetheringSupported(); + parcel.supportedTypes = mSupportedTypeBitmap; parcel.upstreamNetwork = mTetherUpstream; parcel.config = mConfig.toStableParcelable(); parcel.states = @@ -2398,6 +2385,22 @@ public class Tethering { }); } + private void reportTetheringSupportedChange(final long supportedBitmap) { + final int length = mTetheringEventCallbacks.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + try { + mTetheringEventCallbacks.getBroadcastItem(i).onSupportedTetheringTypes( + supportedBitmap); + } catch (RemoteException e) { + // Not really very much to do here. + } + } + } finally { + mTetheringEventCallbacks.finishBroadcast(); + } + } + private void reportUpstreamChanged(UpstreamNetworkState ns) { final int length = mTetheringEventCallbacks.beginBroadcast(); final Network network = (ns != null) ? ns.network : null; @@ -2482,18 +2485,56 @@ public class Tethering { } } + private void updateSupportedDownstreams(final TetheringConfiguration config) { + final long preSupportedBitmap = mSupportedTypeBitmap; + + if (!isTetheringAllowed() || mEntitlementMgr.isProvisioningNeededButUnavailable()) { + mSupportedTypeBitmap = 0; + } else { + mSupportedTypeBitmap = makeSupportedDownstreams(config); + } + + if (preSupportedBitmap != mSupportedTypeBitmap) { + reportTetheringSupportedChange(mSupportedTypeBitmap); + } + } + + private long makeSupportedDownstreams(final TetheringConfiguration config) { + long types = 0; + if (config.tetherableUsbRegexs.length != 0) types |= (1 << TETHERING_USB); + + if (config.tetherableWifiRegexs.length != 0) types |= (1 << TETHERING_WIFI); + + if (config.tetherableBluetoothRegexs.length != 0) types |= (1 << TETHERING_BLUETOOTH); + + // Before T, isTetheringSupported would return true if wifi, usb and bluetooth tethering are + // disabled (whole tethering settings would be hidden). This means tethering would also not + // support wifi p2p, ethernet tethering and mirrorlink. This is wrong but probably there are + // some devices in the field rely on this to disable tethering entirely. + if (!SdkLevel.isAtLeastT() && types == 0) return types; + + if (config.tetherableNcmRegexs.length != 0) types |= (1 << TETHERING_NCM); + + if (config.tetherableWifiP2pRegexs.length != 0) types |= (1 << TETHERING_WIFI_P2P); + + if (isEthernetSupported()) types |= (1 << TETHERING_ETHERNET); + + return types; + } + // if ro.tether.denied = true we default to no tethering // gservices could set the secure setting to 1 though to enable it on a build where it // had previously been turned off. - boolean isTetheringSupported() { + boolean isTetheringAllowed() { final int defaultVal = mDeps.isTetheringDenied() ? 0 : 1; final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.TETHER_SUPPORTED, defaultVal) != 0; - final boolean tetherEnabledInSettings = tetherSupported + return tetherSupported && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING); + } - return tetherEnabledInSettings && hasAnySupportedDownstream() - && !mEntitlementMgr.isProvisioningNeededButUnavailable(); + boolean isTetheringSupported() { + return mSupportedTypeBitmap > 0; } private void dumpBpf(IndentingPrintWriter pw) { diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java index f147e1036d..96ddfa01c4 100644 --- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java +++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java @@ -237,7 +237,7 @@ public class TetheringService extends Service { listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION); return true; } - if (!mTethering.isTetheringSupported()) { + if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) { listener.onResult(TETHER_ERROR_UNSUPPORTED); return true; } @@ -255,7 +255,7 @@ public class TetheringService extends Service { receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null); return true; } - if (!mTethering.isTetheringSupported()) { + if (!mTethering.isTetheringSupported() || !mTethering.isTetheringAllowed()) { receiver.send(TETHER_ERROR_UNSUPPORTED, null); return true; } diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 86dca1c60a..ef4f0527d8 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -192,12 +192,13 @@ public class EthernetTetheringTest { mUiAutomation.adoptShellPermissionIdentity( MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED, ACCESS_NETWORK_STATE, CONNECTIVITY_USE_RESTRICTED_NETWORKS, DUMP); - mRunTests = mTm.isTetheringSupported() && mEm != null; - assumeTrue(mRunTests); - mHandlerThread = new HandlerThread(getClass().getSimpleName()); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + + mRunTests = isEthernetTetheringSupported(); + assumeTrue(mRunTests); + mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm); } @@ -225,7 +226,6 @@ public class EthernetTetheringTest { mHandler.post(() -> reader.stop()); mDownstreamReader = null; } - mHandlerThread.quitSafely(); mTetheredInterfaceRequester.release(); mEm.setIncludeTestInterfaces(false); maybeDeleteTestInterface(); @@ -236,6 +236,7 @@ public class EthernetTetheringTest { try { if (mRunTests) cleanUp(); } finally { + mHandlerThread.quitSafely(); mUiAutomation.dropShellPermissionIdentity(); } } @@ -410,6 +411,23 @@ public class EthernetTetheringTest { // client, which is not possible in this test. } + private boolean isEthernetTetheringSupported() throws Exception { + final CompletableFuture future = new CompletableFuture<>(); + final TetheringEventCallback callback = new TetheringEventCallback() { + @Override + public void onSupportedTetheringTypes(Set supportedTypes) { + future.complete(supportedTypes.contains(TETHERING_ETHERNET)); + } + }; + + try { + mTm.registerTetheringEventCallback(mHandler::post, callback); + return future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); + } finally { + mTm.unregisterTetheringEventCallback(callback); + } + } + private static final class MyTetheringEventCallback implements TetheringEventCallback { private final TetheringManager mTm; private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1); diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java index 9db8f1687e..e114cb5aa7 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java @@ -24,6 +24,7 @@ import static android.net.TetheringManager.TETHERING_WIFI; import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; +import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -139,23 +140,27 @@ public final class TetheringServiceTest { } private void runAsNoPermission(final TestTetheringCall test) throws Exception { - runTetheringCall(test, new String[0]); + runTetheringCall(test, true /* isTetheringAllowed */, new String[0]); } private void runAsTetherPrivileged(final TestTetheringCall test) throws Exception { - runTetheringCall(test, TETHER_PRIVILEGED); + runTetheringCall(test, true /* isTetheringAllowed */, TETHER_PRIVILEGED); } private void runAsAccessNetworkState(final TestTetheringCall test) throws Exception { - runTetheringCall(test, ACCESS_NETWORK_STATE); + runTetheringCall(test, true /* isTetheringAllowed */, ACCESS_NETWORK_STATE); } private void runAsWriteSettings(final TestTetheringCall test) throws Exception { - runTetheringCall(test, WRITE_SETTINGS); + runTetheringCall(test, true /* isTetheringAllowed */, WRITE_SETTINGS); } - private void runTetheringCall(final TestTetheringCall test, String... permissions) - throws Exception { + private void runAsTetheringDisallowed(final TestTetheringCall test) throws Exception { + runTetheringCall(test, false /* isTetheringAllowed */, TETHER_PRIVILEGED); + } + + private void runTetheringCall(final TestTetheringCall test, boolean isTetheringAllowed, + String... permissions) throws Exception { // Allow the test to run even if ACCESS_NETWORK_STATE was granted at the APK level if (!CollectionUtils.contains(permissions, ACCESS_NETWORK_STATE)) { mMockConnector.setPermission(ACCESS_NETWORK_STATE, PERMISSION_DENIED); @@ -164,6 +169,7 @@ public final class TetheringServiceTest { if (permissions.length > 0) mUiAutomation.adoptShellPermissionIdentity(permissions); try { when(mTethering.isTetheringSupported()).thenReturn(true); + when(mTethering.isTetheringAllowed()).thenReturn(isTetheringAllowed); test.runTetheringCall(new TestTetheringResult()); } finally { mUiAutomation.dropShellPermissionIdentity(); @@ -180,6 +186,7 @@ public final class TetheringServiceTest { private void runTether(final TestTetheringResult result) throws Exception { mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).tether(TEST_IFACE_NAME, IpServer.STATE_TETHERED, result); } @@ -203,12 +210,22 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runUnTether(final TestTetheringResult result) throws Exception { mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).untether(eq(TEST_IFACE_NAME), eq(result)); } @@ -232,6 +249,15 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runSetUsbTethering(final TestTetheringResult result) throws Exception { @@ -243,6 +269,7 @@ public final class TetheringServiceTest { mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).setUsbTethering(eq(true) /* enable */, any(IIntResultListener.class)); result.assertResult(TETHER_ERROR_NO_ERROR); } @@ -268,6 +295,14 @@ public final class TetheringServiceTest { verifyNoMoreInteractionsForTethering(); }); + runAsTetheringDisallowed((result) -> { + mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, + TEST_ATTRIBUTION_TAG, result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runStartTethering(final TestTetheringResult result, @@ -275,6 +310,7 @@ public final class TetheringServiceTest { mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result)); } @@ -301,6 +337,15 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runStartTetheringAndVerifyNoPermission(final TestTetheringResult result) @@ -337,6 +382,7 @@ public final class TetheringServiceTest { mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).stopTethering(TETHERING_WIFI); result.assertResult(TETHER_ERROR_NO_ERROR); } @@ -361,6 +407,15 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, + TEST_ATTRIBUTION_TAG, result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runRequestLatestTetheringEntitlementResult() throws Exception { @@ -368,6 +423,7 @@ public final class TetheringServiceTest { mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result, true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI), eq(result), eq(true) /* showEntitlementUi */); } @@ -392,6 +448,16 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((none) -> { + final MyResultReceiver receiver = new MyResultReceiver(null); + mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, + true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + receiver.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runRegisterTetheringEventCallback() throws Exception { @@ -419,6 +485,12 @@ public final class TetheringServiceTest { runRegisterTetheringEventCallback(); verifyNoMoreInteractionsForTethering(); }); + + // should still be able to register callback even tethering is restricted. + runAsTetheringDisallowed((result) -> { + runRegisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); } private void runUnregisterTetheringEventCallback() throws Exception { @@ -446,11 +518,19 @@ public final class TetheringServiceTest { runUnregisterTetheringEventCallback(); verifyNoMoreInteractionsForTethering(); }); + + // should still be able to unregister callback even tethering is restricted. + runAsTetheringDisallowed((result) -> { + runUnregisterTetheringEventCallback(); + verifyNoMoreInteractionsForTethering(); + }); + } private void runStopAllTethering(final TestTetheringResult result) throws Exception { mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); verify(mTethering).untetherAll(); result.assertResult(TETHER_ERROR_NO_ERROR); } @@ -474,11 +554,20 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private void runIsTetheringSupported(final TestTetheringResult result) throws Exception { mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result); verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); result.assertResult(TETHER_ERROR_NO_ERROR); } @@ -502,6 +591,15 @@ public final class TetheringServiceTest { verify(mTethering).isTetherProvisioningRequired(); verifyNoMoreInteractionsForTethering(); }); + + runAsTetheringDisallowed((result) -> { + mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, + result); + verify(mTethering).isTetheringSupported(); + verify(mTethering).isTetheringAllowed(); + result.assertResult(TETHER_ERROR_UNSUPPORTED); + verifyNoMoreInteractionsForTethering(); + }); } private class ConnectorSupplier implements Supplier { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java index b402bc3956..8de783629e 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java @@ -144,6 +144,7 @@ import android.net.TetheredClient.AddressInfo; import android.net.TetheringCallbackStartedParcel; import android.net.TetheringConfigurationParcel; import android.net.TetheringInterface; +import android.net.TetheringManager; import android.net.TetheringRequestParcel; import android.net.dhcp.DhcpLeaseParcelable; import android.net.dhcp.DhcpServerCallbacks; @@ -175,6 +176,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; +import android.util.ArraySet; import androidx.annotation.NonNull; import androidx.test.filters.SmallTest; @@ -217,6 +219,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.Vector; @RunWith(AndroidJUnit4.class) @@ -1729,6 +1732,7 @@ public class TetheringTest { private final ArrayList mTetherStates = new ArrayList<>(); private final ArrayList mOffloadStatus = new ArrayList<>(); private final ArrayList> mTetheredClients = new ArrayList<>(); + private final ArrayList mSupportedBitmaps = 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. @@ -1781,6 +1785,10 @@ public class TetheringTest { assertTrue(leases.containsAll(result)); } + public void expectSupportedTetheringTypes(Set expectedTypes) { + assertEquals(expectedTypes, TetheringManager.unpackBits(mSupportedBitmaps.remove(0))); + } + @Override public void onUpstreamChanged(Network network) { mActualUpstreams.add(network); @@ -1813,11 +1821,17 @@ public class TetheringTest { mTetherStates.add(parcel.states); mOffloadStatus.add(parcel.offloadStatus); mTetheredClients.add(parcel.tetheredClients); + mSupportedBitmaps.add(parcel.supportedTypes); } @Override public void onCallbackStopped(int errorCode) { } + @Override + public void onSupportedTetheringTypes(long supportedBitmap) { + mSupportedBitmaps.add(supportedBitmap); + } + public void assertNoUpstreamChangeCallback() { assertTrue(mActualUpstreams.isEmpty()); } @@ -2945,53 +2959,81 @@ public class TetheringTest { runStopUSBTethering(); } + public static ArraySet getAllSupportedTetheringTypes() { + return new ArraySet<>(new Integer[] { TETHERING_USB, TETHERING_NCM, TETHERING_WIFI, + TETHERING_WIFI_P2P, TETHERING_BLUETOOTH, TETHERING_ETHERNET }); + } + @Test public void testTetheringSupported() throws Exception { + final ArraySet expectedTypes = getAllSupportedTetheringTypes(); + // Check tethering is supported after initialization. setTetheringSupported(true /* supported */); - updateConfigAndVerifySupported(true /* supported */); + TestTetheringEventCallback callback = new TestTetheringEventCallback(); + mTethering.registerTetheringEventCallback(callback); + mLooper.dispatchAll(); + updateConfigAndVerifySupported(callback, expectedTypes); // Could disable tethering supported by settings. Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED, 0); - updateConfigAndVerifySupported(false /* supported */); + updateConfigAndVerifySupported(callback, new ArraySet<>()); // Could disable tethering supported by user restriction. setTetheringSupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); when(mUserManager.hasUserRestriction( UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(true); - updateConfigAndVerifySupported(false /* supported */); + updateConfigAndVerifySupported(callback, new ArraySet<>()); // Tethering is supported if it has any supported downstream. setTetheringSupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); + // Usb tethering is not supported: + expectedTypes.remove(TETHERING_USB); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) .thenReturn(new String[0]); - updateConfigAndVerifySupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); + // Wifi tethering is not supported: + expectedTypes.remove(TETHERING_WIFI); when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) .thenReturn(new String[0]); - updateConfigAndVerifySupported(true /* supported */); - + updateConfigAndVerifySupported(callback, expectedTypes); + // Bluetooth tethering is not supported: + expectedTypes.remove(TETHERING_BLUETOOTH); + when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) + .thenReturn(new String[0]); if (isAtLeastT()) { - when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - updateConfigAndVerifySupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); + + // P2p tethering is not supported: + expectedTypes.remove(TETHERING_WIFI_P2P); when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs)) .thenReturn(new String[0]); - updateConfigAndVerifySupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); + // Ncm tethering is not supported: + expectedTypes.remove(TETHERING_NCM); when(mResources.getStringArray(R.array.config_tether_ncm_regexs)) .thenReturn(new String[0]); - updateConfigAndVerifySupported(true /* supported */); + updateConfigAndVerifySupported(callback, expectedTypes); + // Ethernet tethering (last supported type) is not supported: + expectedTypes.remove(TETHERING_ETHERNET); mForceEthernetServiceUnavailable = true; - updateConfigAndVerifySupported(false /* supported */); + updateConfigAndVerifySupported(callback, new ArraySet<>()); + } else { - when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - updateConfigAndVerifySupported(false /* supported */); + // If wifi, usb and bluetooth are all not supported, all the types are not supported. + expectedTypes.clear(); + updateConfigAndVerifySupported(callback, expectedTypes); } } - private void updateConfigAndVerifySupported(boolean supported) { + private void updateConfigAndVerifySupported(final TestTetheringEventCallback callback, + final ArraySet expectedTypes) { sendConfigurationChanged(); - assertEquals(supported, mTethering.isTetheringSupported()); + + assertEquals(expectedTypes.size() > 0, mTethering.isTetheringSupported()); + callback.expectSupportedTetheringTypes(expectedTypes); } // TODO: Test that a request for hotspot mode doesn't interfere with an // already operating tethering mode interface.