From b6eb2c218b91fcb805c142e8485703e9c353d224 Mon Sep 17 00:00:00 2001 From: markchien Date: Wed, 18 Jul 2018 14:29:20 +0800 Subject: [PATCH] Add Entitlement Manger to encapsulate provisioning mechanics Test: - build, flash, booted - runtest frameworks-net bug: 111490073 Change-Id: I4f67f8679ab91b5c463fc349f5f21aee08bd943e --- .../server/connectivity/Tethering.java | 205 +++------------- .../tethering/EntitlementManager.java | 224 ++++++++++++++++++ .../tethering/TetheringDependencies.java | 15 +- .../server/connectivity/TetheringTest.java | 69 +----- .../tethering/EntitlementManagerTest.java | 144 +++++++++++ 5 files changed, 412 insertions(+), 245 deletions(-) create mode 100644 services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java create mode 100644 tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index a8f7259050c14..3c14393ca7406 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -23,24 +23,18 @@ import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY; import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; -import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; import static android.net.ConnectivityManager.EXTRA_ERRORED_TETHER; import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; -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_MASTER_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL; -import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; -import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; import static android.net.ConnectivityManager.TETHERING_INVALID; import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL; +import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; +import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; @@ -50,6 +44,7 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.IFACE_IP_MODE_UNSPECIFIED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; + import static com.android.server.ConnectivityService.SHORT_ARG; import android.app.Notification; @@ -60,7 +55,6 @@ import android.bluetooth.BluetoothPan; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -68,7 +62,6 @@ import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; -import android.net.ip.IpServer; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -76,7 +69,7 @@ import android.net.Network; import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkUtils; -import android.net.RouteInfo; +import android.net.ip.IpServer; import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; @@ -89,15 +82,12 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.Parcel; -import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -113,6 +103,7 @@ import com.android.internal.util.Protocol; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.LocalServices; +import com.android.server.connectivity.tethering.EntitlementManager; import com.android.server.connectivity.tethering.IPv6TetheringCoordinator; import com.android.server.connectivity.tethering.OffloadController; import com.android.server.connectivity.tethering.TetheringConfiguration; @@ -123,8 +114,6 @@ import com.android.server.net.BaseNetworkObserver; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @@ -145,18 +134,12 @@ public class Tethering extends BaseNetworkObserver { private final static boolean DBG = false; private final static boolean VDBG = false; - protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; - private static final Class[] messageClasses = { Tethering.class, TetherMasterSM.class, IpServer.class }; private static final SparseArray sMagicDecoderRing = MessageUtils.findMessageNames(messageClasses); - // {@link ComponentName} of the Service used to run tether provisioning. - private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources - .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); - private static class TetherState { public final IpServer ipServer; public int lastState; @@ -191,7 +174,6 @@ public class Tethering extends BaseNetworkObserver { private final INetworkStatsService mStatsService; private final INetworkPolicyManager mPolicyManager; private final Looper mLooper; - private final MockableSystemProperties mSystemProperties; private final StateMachine mTetherMasterSM; private final OffloadController mOffloadController; private final UpstreamNetworkMonitor mUpstreamNetworkMonitor; @@ -200,6 +182,7 @@ public class Tethering extends BaseNetworkObserver { private final HashSet mForwardedDownstreams; private final VersionedBroadcastListener mCarrierConfigChange; private final TetheringDependencies mDeps; + private final EntitlementManager mEntitlementMgr; private volatile TetheringConfiguration mConfig; private InterfaceSet mCurrentUpstreamIfaceSet; @@ -220,7 +203,6 @@ public class Tethering extends BaseNetworkObserver { mStatsService = statsService; mPolicyManager = policyManager; mLooper = looper; - mSystemProperties = systemProperties; mDeps = deps; mPublicSync = new Object(); @@ -241,12 +223,13 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); + mEntitlementMgr = mDeps.getEntitlementManager(mContext, mLog, systemProperties); mCarrierConfigChange = new VersionedBroadcastListener( "CarrierConfigChangeListener", mContext, smHandler, filter, (Intent ignored) -> { mLog.log("OBSERVED carrier config change"); updateConfiguration(); - reevaluateSimCardProvisioning(); + mEntitlementMgr.reevaluateSimCardProvisioning(); }); mStateReceiver = new StateReceiver(); @@ -289,6 +272,7 @@ public class Tethering extends BaseNetworkObserver { private void updateConfiguration() { mConfig = new TetheringConfiguration(mContext, mLog); mUpstreamNetworkMonitor.updateMobileRequiresDun(mConfig.isDunRequired); + mEntitlementMgr.updateConfiguration(mConfig); } private void maybeUpdateConfiguration() { @@ -354,83 +338,54 @@ public class Tethering extends BaseNetworkObserver { } public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { - if (!isTetherProvisioningRequired()) { + mEntitlementMgr.startTethering(type); + if (!mEntitlementMgr.isTetherProvisioningRequired()) { enableTetheringInternal(type, true, receiver); return; } + final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); if (showProvisioningUi) { - runUiTetherProvisioningAndEnable(type, receiver); + mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver); } else { - runSilentTetherProvisioningAndEnable(type, receiver); + mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver); } } public void stopTethering(int type) { enableTetheringInternal(type, false, null); - if (isTetherProvisioningRequired()) { - cancelTetherProvisioningRechecks(type); + mEntitlementMgr.stopTethering(type); + if (mEntitlementMgr.isTetherProvisioningRequired()) { + // There are lurking bugs where the notion of "provisioning required" or + // "tethering supported" may change without notifying tethering properly, then + // tethering can't shutdown correctly. + // TODO: cancel re-check all the time + if (mDeps.isTetheringSupported()) { + mEntitlementMgr.cancelTetherProvisioningRechecks(type); + } } } - /** - * Check if the device requires a provisioning check in order to enable tethering. - * - * @return a boolean - {@code true} indicating tether provisioning is required by the carrier. - */ - @VisibleForTesting - protected boolean isTetherProvisioningRequired() { - final TetheringConfiguration cfg = mConfig; - if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) - || cfg.provisioningApp.length == 0) { - return false; - } - if (carrierConfigAffirmsEntitlementCheckNotRequired()) { - return false; - } - return (cfg.provisioningApp.length == 2); - } - - // The logic here is aimed solely at confirming that a CarrierConfig exists - // and affirms that entitlement checks are not required. - // - // TODO: find a better way to express this, or alter the checking process - // entirely so that this is more intuitive. - private boolean carrierConfigAffirmsEntitlementCheckNotRequired() { - // Check carrier config for entitlement checks - final CarrierConfigManager configManager = (CarrierConfigManager) mContext - .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager == null) return false; - - final PersistableBundle carrierConfig = configManager.getConfig(); - if (carrierConfig == null) return false; - - // A CarrierConfigManager was found and it has a config. - final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( - CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - return !isEntitlementCheckRequired; - } - /** * Enables or disables tethering for the given type. This should only be called once * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks * for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = enable && isTetherProvisioningRequired(); + boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired(); int result; switch (type) { case TETHERING_WIFI: result = setWifiTethering(enable); if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - scheduleProvisioningRechecks(type); + mEntitlementMgr.scheduleProvisioningRechecks(type); } sendTetherResult(receiver, result); break; case TETHERING_USB: result = setUsbTethering(enable); if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { - scheduleProvisioningRechecks(type); + mEntitlementMgr.scheduleProvisioningRechecks(type); } sendTetherResult(receiver, result); break; @@ -489,32 +444,14 @@ public class Tethering extends BaseNetworkObserver { ? TETHER_ERROR_NO_ERROR : TETHER_ERROR_MASTER_ERROR; sendTetherResult(receiver, result); - if (enable && isTetherProvisioningRequired()) { - scheduleProvisioningRechecks(TETHERING_BLUETOOTH); + if (enable && mEntitlementMgr.isTetherProvisioningRequired()) { + mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH); } adapter.closeProfileProxy(BluetoothProfile.PAN, proxy); } }, BluetoothProfile.PAN); } - private void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { - ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); - sendUiTetherProvisionIntent(type, proxyReceiver); - } - - private void sendUiTetherProvisionIntent(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); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - /** * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result * is successful before firing back up to the wrapped receiver. @@ -546,62 +483,6 @@ public class Tethering extends BaseNetworkObserver { return receiverForSending; } - private void scheduleProvisioningRechecks(int type) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_SET_ALARM, true); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { - ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); - sendSilentTetherProvisionIntent(type, proxyReceiver); - } - - private void sendSilentTetherProvisionIntent(int type, ResultReceiver receiver) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private void cancelTetherProvisioningRechecks(int type) { - if (mDeps.isTetheringSupported()) { - Intent intent = new Intent(); - intent.putExtra(EXTRA_REM_TETHER_TYPE, type); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - - // Used by the SIM card change observation code. - // TODO: De-duplicate with above code, where possible. - private void startProvisionIntent(int tetherType) { - final Intent startProvIntent = new Intent(); - startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); - startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); - startProvIntent.setComponent(TETHER_SERVICE); - mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); - } - public int tether(String iface) { return tether(iface, IpServer.STATE_TETHERED); } @@ -1166,30 +1047,6 @@ public class Tethering extends BaseNetworkObserver { return false; } - private void reevaluateSimCardProvisioning() { - if (!mConfig.hasMobileHotspotProvisionApp()) return; - if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; - - ArrayList tethered = new ArrayList<>(); - synchronized (mPublicSync) { - for (int i = 0; i < mTetherStates.size(); i++) { - TetherState tetherState = mTetherStates.valueAt(i); - if (tetherState.lastState != IpServer.STATE_TETHERED) { - continue; // Skip interfaces that aren't tethered. - } - String iface = mTetherStates.keyAt(i); - int interfaceType = ifaceNameToType(iface); - if (interfaceType != TETHERING_INVALID) { - tethered.add(interfaceType); - } - } - } - - for (int tetherType : tethered) { - startProvisionIntent(tetherType); - } - } - class TetherMasterSM extends StateMachine { private static final int BASE_MASTER = Protocol.BASE_TETHERING; // an interface SM has requested Tethering/Local Hotspot diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java new file mode 100644 index 0000000000000..a4e3e1d85bcbf --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity.tethering; + +import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE; +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 com.android.internal.R.string.config_wifi_tether_enable; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.util.SharedLog; +import android.os.Binder; +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 com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.connectivity.MockableSystemProperties; + +/** + * This class encapsulates entitlement/provisioning mechanics + * provisioning check only applies to the use of the mobile network as an upstream + * + * @hide + */ +public class EntitlementManager { + private static final String TAG = EntitlementManager.class.getSimpleName(); + + // {@link ComponentName} of the Service used to run tether provisioning. + private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString( + Resources.getSystem().getString(config_wifi_tether_enable)); + protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; + + // The ArraySet contains enabled downstream types, ex: + // {@link ConnectivityManager.TETHERING_WIFI} + // {@link ConnectivityManager.TETHERING_USB} + // {@link ConnectivityManager.TETHERING_BLUETOOTH} + @GuardedBy("mCurrentTethers") + private final ArraySet mCurrentTethers; + private final Context mContext; + private final MockableSystemProperties mSystemProperties; + private final SharedLog mLog; + @Nullable + private TetheringConfiguration mConfig; + + public EntitlementManager(Context ctx, SharedLog log, + MockableSystemProperties systemProperties) { + mContext = ctx; + mLog = log; + mCurrentTethers = new ArraySet(); + mSystemProperties = systemProperties; + } + + /** + * Pass a new TetheringConfiguration instance each time when + * Tethering#updateConfiguration() is called. + */ + public void updateConfiguration(TetheringConfiguration conf) { + mConfig = conf; + } + + /** + * Tell EntitlementManager that a given type of tethering has been enabled + * + * @param type Tethering type + */ + public void startTethering(int type) { + synchronized (mCurrentTethers) { + mCurrentTethers.add(type); + } + } + + /** + * Tell EntitlementManager that a given type of tethering has been disabled + * + * @param type Tethering type + */ + public void stopTethering(int type) { + synchronized (mCurrentTethers) { + mCurrentTethers.remove(type); + } + } + + /** + * Check if the device requires a provisioning check in order to enable tethering. + * + * @return a boolean - {@code true} indicating tether provisioning is required by the carrier. + */ + @VisibleForTesting + public boolean isTetherProvisioningRequired() { + if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) + || mConfig.provisioningApp.length == 0) { + return false; + } + if (carrierConfigAffirmsEntitlementCheckNotRequired()) { + return false; + } + return (mConfig.provisioningApp.length == 2); + } + + /** + * Re-check tethering provisioning for enabled downstream tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + */ + public void reevaluateSimCardProvisioning() { + if (!mConfig.hasMobileHotspotProvisionApp()) return; + if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; + + final ArraySet reevaluateType; + synchronized (mCurrentTethers) { + reevaluateType = new ArraySet(mCurrentTethers); + } + for (Integer type : reevaluateType) { + startProvisionIntent(type); + } + } + + // The logic here is aimed solely at confirming that a CarrierConfig exists + // and affirms that entitlement checks are not required. + // + // TODO: find a better way to express this, or alter the checking process + // entirely so that this is more intuitive. + private boolean carrierConfigAffirmsEntitlementCheckNotRequired() { + // Check carrier config for entitlement checks + final CarrierConfigManager configManager = (CarrierConfigManager) mContext + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager == null) return false; + + final PersistableBundle carrierConfig = configManager.getConfig(); + if (carrierConfig == null) return false; + + // A CarrierConfigManager was found and it has a config. + final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( + CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); + return !isEntitlementCheckRequired; + } + + public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_RUN_PROVISION, true); + intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); + intent.setComponent(TETHER_SERVICE); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + public void runUiTetherProvisioningAndEnable(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); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Used by the SIM card change observation code. + // TODO: De-duplicate with above code, where possible. + private void startProvisionIntent(int tetherType) { + final Intent startProvIntent = new Intent(); + startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); + startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); + startProvIntent.setComponent(TETHER_SERVICE); + mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); + } + + public void scheduleProvisioningRechecks(int type) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_SET_ALARM, true); + intent.setComponent(TETHER_SERVICE); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + public void cancelTetherProvisioningRechecks(int type) { + Intent intent = new Intent(); + intent.putExtra(EXTRA_REM_TETHER_TYPE, type); + intent.setComponent(TETHER_SERVICE); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } +} 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 8b4006916736c..d56b167b9a753 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -17,19 +17,13 @@ package com.android.server.connectivity.tethering; import android.content.Context; -import android.net.INetd; import android.net.NetworkRequest; -import android.net.dhcp.DhcpServer; -import android.net.dhcp.DhcpServingParams; import android.net.ip.IpServer; -import android.net.ip.RouterAdvertisementDaemon; -import android.net.util.InterfaceParams; -import android.net.util.NetdService; -import android.os.Handler; import android.net.util.SharedLog; -import android.os.Looper; +import android.os.Handler; import com.android.internal.util.StateMachine; +import com.android.server.connectivity.MockableSystemProperties; import java.util.ArrayList; @@ -65,4 +59,9 @@ public class TetheringDependencies { public NetworkRequest getDefaultNetworkRequest() { return null; } + + public EntitlementManager getEntitlementManager(Context ctx, SharedLog log, + MockableSystemProperties systemProperties) { + return new EntitlementManager(ctx, log, systemProperties); + } } diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index a6ed9f252008d..80818120fa3c1 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -23,15 +23,15 @@ import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY; import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER; import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER; -import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; -import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI; +import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.ConnectivityManager.TYPE_MOBILE; -import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; -import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; +import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY; +import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; @@ -39,19 +39,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.mock; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -91,9 +90,9 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.PersistableBundle; import android.os.RemoteException; -import android.os.test.TestLooper; import android.os.UserHandle; import android.os.UserManager; +import android.os.test.TestLooper; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -126,7 +125,6 @@ import java.util.Vector; public class TetheringTest { private static final int IFINDEX_OFFSET = 100; - private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0"; private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0"; private static final String TEST_USB_IFNAME = "test_rndis0"; @@ -370,61 +368,6 @@ public class TetheringTest { mServiceContext.unregisterReceiver(mBroadcastReceiver); } - private void setupForRequiredProvisioning() { - // Produce some acceptable looking provision app setting if requested. - when(mResources.getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app)) - .thenReturn(PROVISIONING_APP_NAME); - // Don't disable tethering provisioning unless requested. - when(mSystemProperties.getBoolean(eq(Tethering.DISABLE_PROVISIONING_SYSPROP_KEY), - anyBoolean())).thenReturn(false); - // Act like the CarrierConfigManager is present and ready unless told otherwise. - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(mCarrierConfigManager); - when(mCarrierConfigManager.getConfig()).thenReturn(mCarrierConfig); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); - } - - @Test - public void canRequireProvisioning() { - setupForRequiredProvisioning(); - sendConfigurationChanged(); - assertTrue(mTethering.isTetherProvisioningRequired()); - } - - @Test - public void toleratesCarrierConfigManagerMissing() { - setupForRequiredProvisioning(); - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(null); - sendConfigurationChanged(); - // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. - // We therefore still require provisioning. - assertTrue(mTethering.isTetherProvisioningRequired()); - } - - @Test - public void toleratesCarrierConfigMissing() { - setupForRequiredProvisioning(); - when(mCarrierConfigManager.getConfig()).thenReturn(null); - sendConfigurationChanged(); - // We still have a provisioning app configured, so still require provisioning. - assertTrue(mTethering.isTetherProvisioningRequired()); - } - - @Test - public void provisioningNotRequiredWhenAppNotFound() { - setupForRequiredProvisioning(); - when(mResources.getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app)) - .thenReturn(null); - assertTrue(!mTethering.isTetherProvisioningRequired()); - when(mResources.getStringArray( - com.android.internal.R.array.config_mobile_hotspot_provision_app)) - .thenReturn(new String[] {"malformedApp"}); - assertTrue(!mTethering.isTetherProvisioningRequired()); - } - private void sendWifiApStateChanged(int state) { final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); intent.putExtra(EXTRA_WIFI_AP_STATE, state); diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java new file mode 100644 index 0000000000000..0f72229d38e67 --- /dev/null +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity.tethering; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.util.SharedLog; +import android.os.PersistableBundle; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.CarrierConfigManager; + +import com.android.internal.R; +import com.android.server.connectivity.MockableSystemProperties; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class EntitlementManagerTest { + + private static final int EVENT_EM_UPDATE = 1; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + + @Mock private CarrierConfigManager mCarrierConfigManager; + @Mock private Context mContext; + @Mock private ContentResolver mContent; + @Mock private MockableSystemProperties mSystemProperties; + @Mock private Resources mResources; + @Mock private SharedLog mLog; + + // 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 EntitlementManager mEnMgr; + + @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)) + .thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) + .thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) + .thenReturn(new String[0]); + when(mResources.getIntArray(R.array.config_tether_upstream_types)) + .thenReturn(new int[0]); + when(mLog.forSubComponent(anyString())).thenReturn(mLog); + + mEnMgr = new EntitlementManager(mContext, mLog, mSystemProperties); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + } + + @After + public void tearDown() throws Exception {} + + private void setupForRequiredProvisioning() { + // Produce some acceptable looking provision app setting if requested. + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(PROVISIONING_APP_NAME); + // Don't disable tethering provisioning unless requested. + when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), + anyBoolean())).thenReturn(false); + // Act like the CarrierConfigManager is present and ready unless told otherwise. + when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) + .thenReturn(mCarrierConfigManager); + when(mCarrierConfigManager.getConfig()).thenReturn(mCarrierConfig); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); + } + + @Test + public void canRequireProvisioning() { + setupForRequiredProvisioning(); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + assertTrue(mEnMgr.isTetherProvisioningRequired()); + } + + @Test + public void toleratesCarrierConfigManagerMissing() { + setupForRequiredProvisioning(); + when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) + .thenReturn(null); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. + // Therefore provisioning still be required. + assertTrue(mEnMgr.isTetherProvisioningRequired()); + } + + @Test + public void toleratesCarrierConfigMissing() { + setupForRequiredProvisioning(); + when(mCarrierConfigManager.getConfig()).thenReturn(null); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + // We still have a provisioning app configured, so still require provisioning. + assertTrue(mEnMgr.isTetherProvisioningRequired()); + } + + @Test + public void provisioningNotRequiredWhenAppNotFound() { + setupForRequiredProvisioning(); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(null); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + assertFalse(mEnMgr.isTetherProvisioningRequired()); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(new String[] {"malformedApp"}); + mEnMgr.updateConfiguration(new TetheringConfiguration(mContext, mLog)); + assertFalse(mEnMgr.isTetherProvisioningRequired()); + } + +}