From f27312788000e5974090527b57e98d098f59dfde Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 16 Jan 2019 17:44:13 +0800 Subject: [PATCH] Add get last entitlement value callback API The callback would be used to notify entitlement value. If the cache value indicates entitlement succeeded, it just fire callback with cache value instead of run entitlement check. Bug: 120887283 Test: atest FrameworksNetTests Change-Id: I8afe928423bd75c54c61533a50a5c0814922ceb1 --- api/system-current.txt | 9 + .../java/android/net/ConnectivityManager.java | 64 +++++ .../android/net/IConnectivityManager.aidl | 3 + .../android/server/ConnectivityService.java | 14 ++ .../server/connectivity/Tethering.java | 13 +- .../tethering/EntitlementManager.java | 93 +++++++- .../tethering/TetheringDependencies.java | 6 +- .../tethering/EntitlementManagerTest.java | 219 +++++++++++++++++- 8 files changed, 402 insertions(+), 19 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 20ad25b73f8bc..8b007898c8397 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3067,6 +3067,7 @@ package android.net { method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); + method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported(); method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, "android.permission.NETWORK_STACK"}) public void setAirplaneMode(boolean); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback); @@ -3077,6 +3078,9 @@ package android.net { field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 + field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd + field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 + field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb } public abstract static class ConnectivityManager.OnStartTetheringCallback { @@ -3085,6 +3089,11 @@ package android.net { method public void onTetheringStarted(); } + public abstract static class ConnectivityManager.TetheringEntitlementValueListener { + ctor public ConnectivityManager.TetheringEntitlementValueListener(); + method public void onEntitlementResult(int); + } + public final class IpPrefix implements android.os.Parcelable { ctor public IpPrefix(java.net.InetAddress, int); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 5bb24bab6e48e..2ecc647d64d9f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2544,6 +2544,7 @@ public class ConnectivityManager { } /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_NO_ERROR = 0; /** {@hide} */ public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; @@ -2566,9 +2567,13 @@ public class ConnectivityManager { /** {@hide} */ public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; /** {@hide} */ + @SystemApi public static final int TETHER_ERROR_PROVISION_FAILED = 11; /** {@hide} */ public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; + /** {@hide} */ + @SystemApi + public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; /** * Get a more detailed error code after a Tethering or Untethering @@ -2590,6 +2595,65 @@ public class ConnectivityManager { } } + /** + * Callback for use with {@link #getLatestTetheringEntitlementValue} to find out whether + * entitlement succeeded. + * @hide + */ + @SystemApi + public abstract static class TetheringEntitlementValueListener { + /** + * Called to notify entitlement result. + * + * @param resultCode a int value of entitlement result. It may be one of + * {@link #TETHER_ERROR_NO_ERROR}, + * {@link #TETHER_ERROR_PROVISION_FAILED}, or + * {@link #TETHER_ERROR_ENTITLEMENT_UNKONWN}. + */ + public void onEntitlementResult(int resultCode) {} + } + + /** + * Get the last value of the entitlement check on this downstream. If the cached value is + * {@link #TETHER_ERROR_NO_ERROR} or showEntitlementUi argument is false, it just return the + * cached value. Otherwise, a UI-based entitlement check would be performed. It is not + * guaranteed that the UI-based entitlement check will complete in any specific time period + * and may in fact never complete. Any successful entitlement check the platform performs for + * any reason will update the cached value. + * + * @param type the downstream type of tethering. Must be one of + * {@link #TETHERING_WIFI}, + * {@link #TETHERING_USB}, or + * {@link #TETHERING_BLUETOOTH}. + * @param showEntitlementUi a boolean indicating whether to run UI-based entitlement check. + * @param listener an {@link TetheringEntitlementValueListener} which will be called to notify + * the caller of the result of entitlement check. The listener may be called zero or + * one time. + * @param handler {@link Handler} to specify the thread upon which the listener will be invoked. + * {@hide} + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) + public void getLatestTetheringEntitlementValue(int type, boolean showEntitlementUi, + @NonNull final TetheringEntitlementValueListener listener, @Nullable Handler handler) { + Preconditions.checkNotNull(listener, "TetheringEntitlementValueListener cannot be null."); + ResultReceiver wrappedListener = new ResultReceiver(handler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + listener.onEntitlementResult(resultCode); + } + }; + + try { + String pkgName = mContext.getOpPackageName(); + Log.i(TAG, "getLatestTetheringEntitlementValue:" + pkgName); + mService.getLatestTetheringEntitlementValue(type, wrappedListener, + showEntitlementUi, pkgName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Report network connectivity status. This is currently used only * to alter status bar UI. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index e97060a0a5999..949b772537bbc 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -194,4 +194,7 @@ interface IConnectivityManager int getConnectionOwnerUid(in ConnectionInfo connectionInfo); boolean isCallerCurrentAlwaysOnVpnApp(); boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + + void getLatestTetheringEntitlementValue(int type, in ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 14e235489b97e..bc9f9e3f5a415 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3624,6 +3624,20 @@ public class ConnectivityService extends IConnectivityManager.Stub mTethering.stopTethering(type); } + /** + * Get the latest value of the tethering entitlement check. + * + * Note: Allow privileged apps who have TETHER_PRIVILEGED permission to access. If it turns + * out some such apps are observed to abuse this API, change to per-UID limits on this API + * if it's really needed. + */ + @Override + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi, String callerPkg) { + ConnectivityManager.enforceTetherChangePermission(mContext, callerPkg); + mTethering.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + // Called when we lose the default network and have no replacement yet. // This will automatically be cleared after X seconds or a new default network // becomes CONNECTED, whichever happens first. The timer is started by the diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index eb5be77e4a332..a14fd17209e82 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -121,7 +121,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; - /** * @hide * @@ -223,7 +222,8 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); - mEntitlementMgr = mDeps.getEntitlementManager(mContext, mLog, systemProperties); + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, + mLog, systemProperties); mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, smHandler, filter, (Intent ignored) -> { @@ -470,6 +470,7 @@ public class Tethering extends BaseNetworkObserver { } else { sendTetherResult(receiver, resultCode); } + mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); } }; @@ -1662,6 +1663,14 @@ public class Tethering extends BaseNetworkObserver { mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); } + /** Get the latest value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int type, ResultReceiver receiver, + boolean showEntitlementUi) { + if (receiver != null) { + mEntitlementMgr.getLatestTetheringEntitlementValue(type, receiver, showEntitlementUi); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { // Binder.java closes the resource for us. diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index a4e3e1d85bcbf..75aac106e0e0d 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -21,6 +21,9 @@ import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; import static com.android.internal.R.string.config_wifi_tether_enable; @@ -31,15 +34,21 @@ import android.content.Intent; import android.content.res.Resources; import android.net.util.SharedLog; import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcel; import android.os.PersistableBundle; import android.os.ResultReceiver; import android.os.UserHandle; import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.util.ArraySet; +import android.util.Log; +import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.StateMachine; import com.android.server.connectivity.MockableSystemProperties; /** @@ -50,6 +59,7 @@ import com.android.server.connectivity.MockableSystemProperties; */ public class EntitlementManager { private static final String TAG = EntitlementManager.class.getSimpleName(); + private static final boolean DBG = false; // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( @@ -65,15 +75,19 @@ public class EntitlementManager { private final Context mContext; private final MockableSystemProperties mSystemProperties; private final SharedLog mLog; + private final Handler mMasterHandler; + private final SparseIntArray mEntitlementCacheValue; @Nullable private TetheringConfiguration mConfig; - public EntitlementManager(Context ctx, SharedLog log, + public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, MockableSystemProperties systemProperties) { mContext = ctx; - mLog = log; + mLog = log.forSubComponent(TAG); mCurrentTethers = new ArraySet(); mSystemProperties = systemProperties; + mEntitlementCacheValue = new SparseIntArray(); + mMasterHandler = tetherMasterSM.getHandler(); } /** @@ -128,6 +142,10 @@ public class EntitlementManager { * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. */ public void reevaluateSimCardProvisioning() { + synchronized (mEntitlementCacheValue) { + mEntitlementCacheValue.clear(); + } + if (!mConfig.hasMobileHotspotProvisionApp()) return; if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; @@ -175,6 +193,11 @@ public class EntitlementManager { } public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + runUiTetherProvisioning(type, receiver); + } + + @VisibleForTesting + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,4 +244,70 @@ public class EntitlementManager { Binder.restoreCallingIdentity(ident); } } + + private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mMasterHandler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); + receiver.send(updatedCacheValue, null); + } + }; + + return writeToParcel(rr); + } + + private ResultReceiver writeToParcel(final ResultReceiver receiver) { + // This is necessary to avoid unmarshalling issues when sending the receiver + // across processes. + Parcel parcel = Parcel.obtain(); + receiver.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return receiverForSending; + } + + /** + * Update the last entitlement value to internal cache + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode last entitlement value + * @return the last updated entitlement value + */ + public int updateEntitlementCacheValue(int type, int resultCode) { + if (DBG) { + Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode); + } + synchronized (mEntitlementCacheValue) { + if (resultCode == TETHER_ERROR_NO_ERROR) { + mEntitlementCacheValue.put(type, resultCode); + return resultCode; + } else { + mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); + return TETHER_ERROR_PROVISION_FAILED; + } + } + } + + /** Get the last value of the tethering entitlement check. */ + public void getLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, + boolean showEntitlementUi) { + if (!isTetherProvisioningRequired()) { + receiver.send(TETHER_ERROR_NO_ERROR, null); + return; + } + + final int cacheValue; + synchronized (mEntitlementCacheValue) { + cacheValue = mEntitlementCacheValue.get( + downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); + } + if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { + receiver.send(cacheValue, null); + } else { + ResultReceiver proxy = buildProxyReceiver(downstream, receiver); + runUiTetherProvisioning(downstream, proxy); + } + } } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index a42efe960ff9c..6d6f81eb98e6c 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -81,8 +81,8 @@ public class TetheringDependencies { /** * Get a reference to the EntitlementManager to be used by tethering. */ - public EntitlementManager getEntitlementManager(Context ctx, SharedLog log, - MockableSystemProperties systemProperties) { - return new EntitlementManager(ctx, log, systemProperties); + public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, target, log, systemProperties); } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index 0f72229d38e67..ec286759354aa 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,8 +16,16 @@ package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -27,12 +35,22 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.util.SharedLog; +import android.os.Bundle; +import android.os.Message; import android.os.PersistableBundle; +import android.os.ResultReceiver; +import android.os.test.TestLooper; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.telephony.CarrierConfigManager; +import android.test.mock.MockContentResolver; import com.android.internal.R; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.connectivity.MockableSystemProperties; import org.junit.After; @@ -42,6 +60,10 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @RunWith(AndroidJUnit4.class) @SmallTest public final class EntitlementManagerTest { @@ -51,7 +73,6 @@ public final class EntitlementManagerTest { @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; - @Mock private ContentResolver mContent; @Mock private MockableSystemProperties mSystemProperties; @Mock private Resources mResources; @Mock private SharedLog mLog; @@ -59,15 +80,49 @@ public final class EntitlementManagerTest { // 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(); + private final TestLooper mLooper = new TestLooper(); + private Context mMockContext; + private MockContentResolver mContentResolver; - private EntitlementManager mEnMgr; + private TestStateMachine mSM; + private WrappedEntitlementManager mEnMgr; + + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + } + + public class WrappedEntitlementManager extends EntitlementManager { + public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + public boolean everRunUiEntitlement = false; + + public WrappedEntitlementManager(Context ctx, StateMachine target, + SharedLog log, MockableSystemProperties systemProperties) { + super(ctx, target, log, systemProperties); + } + + @Override + protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { + everRunUiEntitlement = true; + receiver.send(fakeEntitlementResult, null); + } + } @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mContext.getResources()).thenReturn(mResources); - when(mContext.getContentResolver()).thenReturn(mContent); when(mResources.getStringArray(R.array.config_tether_dhcp_range)) .thenReturn(new String[0]); when(mResources.getStringArray(R.array.config_tether_usb_regexs)) @@ -80,12 +135,21 @@ public final class EntitlementManagerTest { .thenReturn(new int[0]); when(mLog.forSubComponent(anyString())).thenReturn(mLog); - mEnMgr = new EntitlementManager(mContext, mLog, mSystemProperties); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mMockContext = new MockContext(mContext); + mSM = new TestStateMachine(); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); } @After - public void tearDown() throws Exception {} + public void tearDown() throws Exception { + if (mSM != null) { + mSM.quit(); + mSM = null; + } + } private void setupForRequiredProvisioning() { // Produce some acceptable looking provision app setting if requested. @@ -104,7 +168,7 @@ public final class EntitlementManagerTest { @Test public void canRequireProvisioning() { setupForRequiredProvisioning(); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -113,7 +177,7 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. // Therefore provisioning still be required. assertTrue(mEnMgr.isTetherProvisioningRequired()); @@ -123,7 +187,7 @@ public final class EntitlementManagerTest { public void toleratesCarrierConfigMissing() { setupForRequiredProvisioning(); when(mCarrierConfigManager.getConfig()).thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); // We still have a provisioning app configured, so still require provisioning. assertTrue(mEnMgr.isTetherProvisioningRequired()); } @@ -133,12 +197,143 @@ public final class EntitlementManagerTest { setupForRequiredProvisioning(); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(null); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertFalse(mEnMgr.isTetherProvisioningRequired()); when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) .thenReturn(new String[] {"malformedApp"}); - mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); assertFalse(mEnMgr.isTetherProvisioningRequired()); } + @Test + public void testGetLastEntitlementCacheValue() throws Exception { + final CountDownLatch mCallbacklatch = new CountDownLatch(1); + // 1. Entitlement check is not required. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog)); + // 2. No cache value and don't need to run entitlement check. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 3. No cache value and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertTrue(mEnMgr.everRunUiEntitlement); + // 6. Cache value is TETHER_ERROR_NO_ERROR. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_WIFI, receiver, true); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + // 7. Test get value for other downstream type. + mEnMgr.everRunUiEntitlement = false; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementValue(TETHERING_USB, receiver, false); + callbackTimeoutHelper(mCallbacklatch); + assertFalse(mEnMgr.everRunUiEntitlement); + } + + void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { + if (!latch.await(1, TimeUnit.SECONDS)) { + fail("Timout, fail to recieve callback"); + } + } + public class TestStateMachine extends StateMachine { + public final ArrayList messages = new ArrayList<>(); + private final State mLoggingState = + new EntitlementManagerTest.TestStateMachine.LoggingState(); + + class LoggingState extends State { + @Override public void enter() { + messages.clear(); + } + + @Override public void exit() { + messages.clear(); + } + + @Override public boolean processMessage(Message msg) { + messages.add(msg); + return false; + } + } + + public TestStateMachine() { + super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); + addState(mLoggingState); + setInitialState(mLoggingState); + super.start(); + } + } }