diff options
8 files changed, 402 insertions, 19 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 17dcda1e248c..05e78ee1e12f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3070,6 +3070,7 @@ package android.net { method @RequiresPermission(android.Manifest.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); @@ -3080,6 +3081,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 { @@ -3088,6 +3092,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); ctor public IpPrefix(String); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index f47ada6ae557..f807924e752d 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2581,6 +2581,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; @@ -2603,9 +2604,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 @@ -2628,6 +2633,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. * <p>This method requires the caller to hold the permission diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index fd7360fd4c17..78fafebc4f37 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -197,4 +197,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 9892bfa50189..08e49034aa6c 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3643,6 +3643,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 eb5be77e4a33..a14fd17209e8 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 a4e3e1d85bcb..75aac106e0e0 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<Integer>(); 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 a42efe960ff9..6d6f81eb98e6 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 0f72229d38e6..ec286759354a 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 TestStateMachine mSM; + private WrappedEntitlementManager mEnMgr; - private EntitlementManager 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<Message> 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(); + } + } } |