From a84bb2d8ba42701287ac71bd4e5be952c580525f Mon Sep 17 00:00:00 2001 From: Meng Wang Date: Tue, 1 Feb 2022 23:17:25 +0000 Subject: [PATCH] Add a carrier config to block tethering. When KEY_CARRIER_SUPPORTS_TETHERING_BOOL=false, it will behave as if entitlement check failed and a system notifcation shows up if user tries to turn on tethering from Settings UI. Bug: 161206517 Test: manual Change-Id: I78da390ecb0c8f4f68285ef16da80653f136c11e --- .../tethering/EntitlementManager.java | 177 ++++++++++++++---- .../networkstack/tethering/Tethering.java | 26 +-- .../tethering/EntitlementManagerTest.java | 126 +++++++++++-- 3 files changed, 257 insertions(+), 72 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java index 4ca36df967..844efdec5a 100644 --- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java +++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java @@ -16,6 +16,7 @@ package com.android.networkstack.tethering; +import static android.content.pm.PackageManager.GET_ACTIVITIES; import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE; import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK; import static android.net.TetheringConstants.EXTRA_RUN_PROVISION; @@ -32,6 +33,9 @@ import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN; import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR; import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED; +import static com.android.networkstack.apishim.ConstantsShim.ACTION_TETHER_UNSUPPORTED_CARRIER_UI; +import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; + import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -39,6 +43,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.util.SharedLog; import android.os.Bundle; import android.os.Handler; @@ -52,6 +57,7 @@ import android.telephony.CarrierConfigManager; import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import java.io.PrintWriter; import java.util.BitSet; @@ -74,6 +80,13 @@ public class EntitlementManager { protected static final String ACTION_PROVISIONING_ALARM = "com.android.networkstack.tethering.PROVISIONING_RECHECK_ALARM"; + // Indicate tether provisioning is not required by carrier. + private static final int TETHERING_PROVISIONING_REQUIRED = 1000; + // Indicate tether provisioning is required by carrier. + private static final int TETHERING_PROVISIONING_NOT_REQUIRED = 1001; + // Indicate tethering is not supported by carrier. + private static final int TETHERING_PROVISIONING_CARRIER_UNSUPPORT = 1002; + private final ComponentName mSilentProvisioningService; private static final int MS_PER_HOUR = 60 * 60 * 1000; private static final int DUMP_TIMEOUT = 10_000; @@ -96,7 +109,7 @@ public class EntitlementManager { private boolean mLastCellularUpstreamPermitted = true; private boolean mUsingCellularAsUpstream = false; private boolean mNeedReRunProvisioningUi = false; - private OnUiEntitlementFailedListener mListener; + private OnTetherProvisioningFailedListener mListener; private TetheringConfigurationFetcher mFetcher; public EntitlementManager(Context ctx, Handler h, SharedLog log, @@ -115,18 +128,20 @@ public class EntitlementManager { mContext.getResources().getString(R.string.config_wifi_tether_enable)); } - public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { + public void setOnTetherProvisioningFailedListener( + final OnTetherProvisioningFailedListener listener) { mListener = listener; } /** Callback fired when UI entitlement failed. */ - public interface OnUiEntitlementFailedListener { + public interface OnTetherProvisioningFailedListener { /** * Ui entitlement check fails in |downstream|. * * @param downstream tethering type from TetheringManager.TETHERING_{@code *}. + * @param reason Failed reason. */ - void onUiEntitlementFailed(int downstream); + void onTetherProvisioningFailed(int downstream, String reason); } public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) { @@ -153,6 +168,9 @@ public class EntitlementManager { } private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) { + // If #getTetherProvisioningCondition return TETHERING_PROVISIONING_CARRIER_UNSUPPORT, + // that means cellular upstream is not supported and entitlement check result is empty + // because entitlement check should not be run. if (!isTetherProvisioningRequired(config)) return true; // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular @@ -199,11 +217,7 @@ public class EntitlementManager { // If upstream is not cellular, provisioning app would not be launched // till upstream change to cellular. if (mUsingCellularAsUpstream) { - if (showProvisioningUi) { - runUiTetherProvisioning(downstreamType, config); - } else { - runSilentTetherProvisioning(downstreamType, config); - } + runTetheringProvisioning(showProvisioningUi, downstreamType, config); mNeedReRunProvisioningUi = false; } else { mNeedReRunProvisioningUi |= showProvisioningUi; @@ -262,17 +276,50 @@ public class EntitlementManager { // the change and get the new correct value. for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0; downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) { + // If tethering provisioning is required but entitlement check result is empty, + // this means tethering may need to run entitlement check or carrier network + // is not supported. if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) { - if (mNeedReRunProvisioningUi) { - mNeedReRunProvisioningUi = false; - runUiTetherProvisioning(downstream, config); - } else { - runSilentTetherProvisioning(downstream, config); - } + runTetheringProvisioning(mNeedReRunProvisioningUi, downstream, config); + mNeedReRunProvisioningUi = false; } } } + /** + * Tether provisioning has these conditions to control provisioning behavior. + * 1st priority : Uses system property to disable any provisioning behavior. + * 2nd priority : Uses {@code CarrierConfigManager#KEY_CARRIER_SUPPORTS_TETHERING_BOOL} to + * decide current carrier support cellular upstream tethering or not. + * If value is true, it means check follow up condition to know whether + * provisioning is required. + * If value is false, it means tethering could not use cellular as upstream. + * 3rd priority : Uses {@code CarrierConfigManager#KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL} to + * decide current carrier require the provisioning. + * 4th priority : Checks whether provisioning is required from RRO configuration. + * + * @param config + * @return integer {@see #TETHERING_PROVISIONING_NOT_REQUIRED, + * #TETHERING_PROVISIONING_REQUIRED, + * #TETHERING_PROVISIONING_CARRIER_UNSUPPORT} + */ + private int getTetherProvisioningCondition(final TetheringConfiguration config) { + if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false)) { + return TETHERING_PROVISIONING_NOT_REQUIRED; + } + // TODO: Find a way to avoid get carrier config twice. + if (carrierConfigAffirmsCarrierNotSupport(config)) { + // To block tethering, behave as if running provisioning check and failed. + return TETHERING_PROVISIONING_CARRIER_UNSUPPORT; + } + + if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) { + return TETHERING_PROVISIONING_NOT_REQUIRED; + } + return (config.provisioningApp.length == 2) + ? TETHERING_PROVISIONING_REQUIRED : TETHERING_PROVISIONING_NOT_REQUIRED; + } + /** * Check if the device requires a provisioning check in order to enable tethering. * @@ -281,14 +328,26 @@ public class EntitlementManager { */ @VisibleForTesting protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) { - if (SystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) - || config.provisioningApp.length == 0) { + return getTetherProvisioningCondition(config) != TETHERING_PROVISIONING_NOT_REQUIRED; + } + + /** + * Confirms the need of tethering provisioning but no entitlement package exists. + */ + public boolean isProvisioningNeededButUnavailable() { + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + return getTetherProvisioningCondition(config) == TETHERING_PROVISIONING_REQUIRED + && !doesEntitlementPackageExist(config); + } + + private boolean doesEntitlementPackageExist(final TetheringConfiguration config) { + final PackageManager pm = mContext.getPackageManager(); + try { + pm.getPackageInfo(config.provisioningApp[0], GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException e) { return false; } - if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) { - return false; - } - return (config.provisioningApp.length == 2); + return true; } /** @@ -310,9 +369,7 @@ public class EntitlementManager { mEntitlementCacheValue.clear(); mCurrentEntitlementResults.clear(); - // TODO: refine provisioning check to isTetherProvisioningRequired() ?? - if (!config.hasMobileHotspotProvisionApp() - || carrierConfigAffirmsEntitlementCheckNotRequired(config)) { + if (!isTetherProvisioningRequired(config)) { evaluateCellularPermission(config); return; } @@ -327,8 +384,8 @@ public class EntitlementManager { * @param config an object that encapsulates the various tethering configuration elements. * */ public PersistableBundle getCarrierConfig(final TetheringConfiguration config) { - final CarrierConfigManager configManager = (CarrierConfigManager) mContext - .getSystemService(Context.CARRIER_CONFIG_SERVICE); + final CarrierConfigManager configManager = mContext + .getSystemService(CarrierConfigManager.class); if (configManager == null) return null; final PersistableBundle carrierConfig = configManager.getConfigForSubId( @@ -346,6 +403,7 @@ public class EntitlementManager { // // TODO: find a better way to express this, or alter the checking process // entirely so that this is more intuitive. + // TODO: Find a way to avoid using getCarrierConfig everytime. private boolean carrierConfigAffirmsEntitlementCheckNotRequired( final TetheringConfiguration config) { // Check carrier config for entitlement checks @@ -358,16 +416,29 @@ public class EntitlementManager { return !isEntitlementCheckRequired; } + private boolean carrierConfigAffirmsCarrierNotSupport(final TetheringConfiguration config) { + if (!SdkLevel.isAtLeastT()) { + return false; + } + // Check carrier config for entitlement checks + final PersistableBundle carrierConfig = getCarrierConfig(config); + if (carrierConfig == null) return false; + + // A CarrierConfigManager was found and it has a config. + final boolean mIsCarrierSupport = carrierConfig.getBoolean( + KEY_CARRIER_SUPPORTS_TETHERING_BOOL, true); + return !mIsCarrierSupport; + } + /** * Run no UI tethering provisioning check. * @param type tethering type from TetheringManager.TETHERING_{@code *} * @param subId default data subscription ID. */ @VisibleForTesting - protected Intent runSilentTetherProvisioning(int type, final TetheringConfiguration config) { + protected Intent runSilentTetherProvisioning( + int type, final TetheringConfiguration config, ResultReceiver receiver) { if (DBG) mLog.i("runSilentTetherProvisioning: " + type); - // For silent provisioning, settings would stop tethering when entitlement fail. - ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); Intent intent = new Intent(); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); @@ -383,11 +454,6 @@ public class EntitlementManager { return intent; } - private void runUiTetherProvisioning(int type, final TetheringConfiguration config) { - ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, config, receiver); - } - /** * Run the UI-enabled tethering provisioning check. * @param type tethering type from TetheringManager.TETHERING_{@code *} @@ -411,6 +477,35 @@ public class EntitlementManager { return intent; } + private void runTetheringProvisioning( + boolean showProvisioningUi, int downstreamType, final TetheringConfiguration config) { + if (carrierConfigAffirmsCarrierNotSupport(config)) { + mListener.onTetherProvisioningFailed(downstreamType, "Carrier does not support."); + if (showProvisioningUi) { + showCarrierUnsupportedDialog(); + } + return; + } + + ResultReceiver receiver = + buildProxyReceiver(downstreamType, showProvisioningUi/* notifyFail */, null); + if (showProvisioningUi) { + runUiTetherProvisioning(downstreamType, config, receiver); + } else { + runSilentTetherProvisioning(downstreamType, config, receiver); + } + } + + private void showCarrierUnsupportedDialog() { + // This is only used when carrierConfigAffirmsCarrierNotSupport() is true. + if (!SdkLevel.isAtLeastT()) { + return; + } + Intent intent = new Intent(ACTION_TETHER_UNSUPPORTED_CARRIER_UI); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } + @VisibleForTesting PendingIntent createRecheckAlarmIntent() { final Intent intent = new Intent(ACTION_PROVISIONING_ALARM); @@ -576,7 +671,8 @@ public class EntitlementManager { int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); addDownstreamMapping(type, updatedCacheValue); if (updatedCacheValue == TETHER_ERROR_PROVISIONING_FAILED && notifyFail) { - mListener.onUiEntitlementFailed(type); + mListener.onTetherProvisioningFailed( + type, "Tethering provisioning failed."); } if (receiver != null) receiver.send(updatedCacheValue, null); } @@ -632,9 +728,14 @@ public class EntitlementManager { } final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (!isTetherProvisioningRequired(config)) { - receiver.send(TETHER_ERROR_NO_ERROR, null); - return; + + switch (getTetherProvisioningCondition(config)) { + case TETHERING_PROVISIONING_NOT_REQUIRED: + receiver.send(TETHER_ERROR_NO_ERROR, null); + return; + case TETHERING_PROVISIONING_CARRIER_UNSUPPORT: + receiver.send(TETHER_ERROR_PROVISIONING_FAILED, null); + return; } final int cacheValue = mEntitlementCacheValue.get( diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index bb9b6fbeaa..0b607bd038 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -18,7 +18,6 @@ package com.android.networkstack.tethering; import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_STACK; -import static android.content.pm.PackageManager.GET_ACTIVITIES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.hardware.usb.UsbManager.USB_CONFIGURED; import static android.hardware.usb.UsbManager.USB_CONNECTED; @@ -319,8 +318,8 @@ public class Tethering { mEntitlementMgr = mDeps.getEntitlementManager(mContext, mHandler, mLog, () -> mTetherMainSM.sendMessage( TetherMainSM.EVENT_UPSTREAM_PERMISSION_CHANGED)); - mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> { - mLog.log("OBSERVED UiEnitlementFailed"); + mEntitlementMgr.setOnTetherProvisioningFailedListener((downstream, reason) -> { + mLog.log("OBSERVED OnTetherProvisioningFailed : " + reason); stopTethering(downstream); }); mEntitlementMgr.setTetheringConfigurationFetcher(() -> { @@ -995,30 +994,11 @@ public class Tethering { return tetherState.lastError; } - private boolean isProvisioningNeededButUnavailable() { - return isTetherProvisioningRequired() && !doesEntitlementPackageExist(); - } - boolean isTetherProvisioningRequired() { final TetheringConfiguration cfg = mConfig; return mEntitlementMgr.isTetherProvisioningRequired(cfg); } - private boolean doesEntitlementPackageExist() { - // provisioningApp must contain package and class name. - if (mConfig.provisioningApp.length != 2) { - return false; - } - - final PackageManager pm = mContext.getPackageManager(); - try { - pm.getPackageInfo(mConfig.provisioningApp[0], GET_ACTIVITIES); - } catch (PackageManager.NameNotFoundException e) { - return false; - } - return true; - } - private int getRequestedState(int type) { final TetheringRequestParcel request = mActiveTetheringRequests.get(type); @@ -2476,7 +2456,7 @@ public class Tethering { && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING); return tetherEnabledInSettings && hasAnySupportedDownstream() - && !isProvisioningNeededButUnavailable(); + && !mEntitlementMgr.isProvisioningNeededButUnavailable(); } private void dumpBpf(IndentingPrintWriter pw) { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java index 46ce82cf7a..690ff71912 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java @@ -37,6 +37,9 @@ import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.networkstack.apishim.ConstantsShim.KEY_CARRIER_SUPPORTS_TETHERING_BOOL; +import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -47,6 +50,7 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -77,9 +81,12 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.modules.utils.build.SdkLevel; +import com.android.testutils.DevSdkIgnoreRule; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -96,6 +103,7 @@ public final class EntitlementManagerTest { private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; private static final String PROVISIONING_APP_RESPONSE = "app_response"; private static final String TEST_PACKAGE_NAME = "com.android.tethering.test"; + private static final String FAILED_TETHERING_REASON = "Tethering provisioning failed."; private static final int RECHECK_TIMER_HOURS = 24; @Mock private CarrierConfigManager mCarrierConfigManager; @@ -103,10 +111,14 @@ public final class EntitlementManagerTest { @Mock private Resources mResources; @Mock private SharedLog mLog; @Mock private PackageManager mPm; - @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; + @Mock private EntitlementManager + .OnTetherProvisioningFailedListener mTetherProvisioningFailedListener; @Mock private AlarmManager mAlarmManager; @Mock private PendingIntent mAlarmIntent; + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + // Like so many Android system APIs, these cannot be mocked because it is marked final. // We have to use the real versions. private final PersistableBundle mCarrierConfig = new PersistableBundle(); @@ -179,8 +191,8 @@ public final class EntitlementManagerTest { @Override protected Intent runSilentTetherProvisioning(int type, - final TetheringConfiguration config) { - Intent intent = super.runSilentTetherProvisioning(type, config); + final TetheringConfiguration config, final ResultReceiver receiver) { + Intent intent = super.runSilentTetherProvisioning(type, config, receiver); assertSilentTetherProvisioning(type, config, intent); silentProvisionCount++; addDownstreamMapping(type, fakeEntitlementResult); @@ -245,7 +257,7 @@ public final class EntitlementManagerTest { mPermissionChangeCallback = spy(() -> { }); mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog, mPermissionChangeCallback); - mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); + mEnMgr.setOnTetherProvisioningFailedListener(mTetherProvisioningFailedListener); mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); mEnMgr.setTetheringConfigurationFetcher(() -> { return mConfig; @@ -268,14 +280,23 @@ public final class EntitlementManagerTest { when(mResources.getInteger(R.integer.config_mobile_hotspot_provision_check_period)) .thenReturn(RECHECK_TIMER_HOURS); // Act like the CarrierConfigManager is present and ready unless told otherwise. - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(mCarrierConfigManager); + mockService(Context.CARRIER_CONFIG_SERVICE, + CarrierConfigManager.class, mCarrierConfigManager); when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig); mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); } + private void setupCarrierConfig(boolean carrierSupported) { + mCarrierConfig.putBoolean(KEY_CARRIER_SUPPORTS_TETHERING_BOOL, carrierSupported); + } + + private void mockService(String serviceName, Class serviceClass, T service) { + when(mMockContext.getSystemServiceName(serviceClass)).thenReturn(serviceName); + when(mMockContext.getSystemService(serviceName)).thenReturn(service); + } + @Test public void canRequireProvisioning() { setupForRequiredProvisioning(); @@ -285,8 +306,7 @@ public final class EntitlementManagerTest { @Test public void toleratesCarrierConfigManagerMissing() { setupForRequiredProvisioning(); - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(null); + mockService(Context.CARRIER_CONFIG_SERVICE, CarrierConfigManager.class, null); mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. // Therefore provisioning still be required. @@ -613,14 +633,16 @@ public final class EntitlementManagerTest { @Test public void testCallStopTetheringWhenUiProvisioningFail() { setupForRequiredProvisioning(); - verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); + verify(mTetherProvisioningFailedListener, times(0)) + .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON); mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED; mEnMgr.notifyUpstream(true); mLooper.dispatchAll(); mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); mLooper.dispatchAll(); assertEquals(1, mEnMgr.uiProvisionCount); - verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); + verify(mTetherProvisioningFailedListener, times(1)) + .onTetherProvisioningFailed(TETHERING_WIFI, FAILED_TETHERING_REASON); } @Test @@ -644,7 +666,8 @@ public final class EntitlementManagerTest { // When second downstream is down, exempted downstream can use cellular upstream. assertEquals(1, mEnMgr.uiProvisionCount); - verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB); + verify(mTetherProvisioningFailedListener).onTetherProvisioningFailed(TETHERING_USB, + FAILED_TETHERING_REASON); mEnMgr.stopProvisioningIfNeeded(TETHERING_USB); assertTrue(mEnMgr.isCellularUpstreamPermitted()); @@ -678,4 +701,85 @@ public final class EntitlementManagerTest { verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), eq(mAlarmIntent)); } + + @Test + @IgnoreUpTo(SC_V2) + public void requestLatestTetheringEntitlementResult_carrierDoesNotSupport_noProvisionCount() + throws Exception { + setupForRequiredProvisioning(); + setupCarrierConfig(false); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode); + } + }; + mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + } + + @Test + @IgnoreUpTo(SC_V2) + public void reevaluateSimCardProvisioning_carrierUnsupportAndSimswitch() { + setupForRequiredProvisioning(); + + // Start a tethering with cellular data without provisioning. + mEnMgr.notifyUpstream(true); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); + mLooper.dispatchAll(); + + // Tear down mobile, then switch SIM. + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + setupCarrierConfig(false); + mEnMgr.reevaluateSimCardProvisioning(mConfig); + + // Turn on upstream. + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + + verify(mTetherProvisioningFailedListener) + .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support."); + } + + @Test + @IgnoreUpTo(SC_V2) + public void startProvisioningIfNeeded_carrierUnsupport() + throws Exception { + setupForRequiredProvisioning(); + setupCarrierConfig(false); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + verify(mTetherProvisioningFailedListener, never()) + .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support."); + + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + verify(mTetherProvisioningFailedListener) + .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support."); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + reset(mTetherProvisioningFailedListener); + + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + verify(mTetherProvisioningFailedListener) + .onTetherProvisioningFailed(TETHERING_WIFI, "Carrier does not support."); + } + + @Test + public void isTetherProvisioningRequired_carrierUnSupport() { + setupForRequiredProvisioning(); + setupCarrierConfig(false); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(new String[0]); + mConfig = new FakeTetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + + if (SdkLevel.isAtLeastT()) { + assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); + } else { + assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + } }