Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1c66fd2d authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add Entitlement Manger to encapsulate provisioning mechanics"

parents 67c6ab38 b6eb2c21
Loading
Loading
Loading
Loading
+31 −174
Original line number Diff line number Diff line
@@ -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<String> 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<IpServer> 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,61 +338,32 @@ 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);
        }
    }

    /**
     * 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;
        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);
            }
        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;
    }

    /**
@@ -417,20 +372,20 @@ public class Tethering extends BaseNetworkObserver {
     * 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<Integer> 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
+224 −0

File added.

Preview size limit exceeded, changes collapsed.

+7 −8
Original line number Diff line number Diff line
@@ -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);
    }
}
+6 −63
Original line number Diff line number Diff line
@@ -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);
+144 −0

File added.

Preview size limit exceeded, changes collapsed.