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

Commit 908ce167 authored by Erik Kline's avatar Erik Kline Committed by android-build-merger
Browse files

Merge "Tethering: support Local-only Hotspot mode for downstreams"

am: d3128d4b

Change-Id: Ic7d5e3e23f9ccc0f094ba5991e2fdefa82865b01
parents 0ad718aa d3128d4b
Loading
Loading
Loading
Loading
+162 −50
Original line number Diff line number Diff line
@@ -90,7 +90,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;


@@ -122,12 +124,23 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        public final TetherInterfaceStateMachine stateMachine;
        public int lastState;
        public int lastError;

        public TetherState(TetherInterfaceStateMachine sm) {
            stateMachine = sm;
            // Assume all state machines start out available and with no errors.
            lastState = IControlsTethering.STATE_AVAILABLE;
            lastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
        }

        public boolean isCurrentlyServing() {
            switch (lastState) {
                case IControlsTethering.STATE_TETHERED:
                case IControlsTethering.STATE_LOCAL_HOTSPOT:
                    return true;
                default:
                    return false;
            }
        }
    }

    // used to synchronize public access to members
@@ -143,11 +156,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
    private final StateMachine mTetherMasterSM;
    private final OffloadController mOffloadController;
    private final UpstreamNetworkMonitor mUpstreamNetworkMonitor;
    private final HashSet<TetherInterfaceStateMachine> mForwardedDownstreams;

    private volatile TetheringConfiguration mConfig;
    private String mCurrentUpstreamIface;
    private Notification.Builder mTetheredNotificationBuilder;
    private int mLastNotificationId;

    private boolean mRndisEnabled;       // track the RNDIS function enabled state
    private boolean mUsbTetherRequested; // true if USB tethering should be started
                                         // when RNDIS is enabled
@@ -174,6 +189,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        mOffloadController = new OffloadController(mTetherMasterSM.getHandler());
        mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
                mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
        mForwardedDownstreams = new HashSet<>();

        mStateReceiver = new StateReceiver();
        IntentFilter filter = new IntentFilter();
@@ -511,6 +527,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
    }

    public int tether(String iface) {
        return tether(iface, IControlsTethering.STATE_TETHERED);
    }

    private int tether(String iface, int requestedState) {
        if (DBG) Log.d(TAG, "Tethering " + iface);
        synchronized (mPublicSync) {
            TetherState tetherState = mTetherStates.get(iface);
@@ -524,7 +544,13 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                Log.e(TAG, "Tried to Tether an unavailable iface: " + iface + ", ignoring");
                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
            }
            tetherState.stateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
            // NOTE: If a CMD_TETHER_REQUESTED message is already in the TISM's
            // queue but not yet processed, this will be a no-op and it will not
            // return an error.
            //
            // TODO: reexamine the threading and messaging model.
            tetherState.stateMachine.sendMessage(
                    TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, requestedState);
            return ConnectivityManager.TETHER_ERROR_NO_ERROR;
        }
    }
@@ -537,8 +563,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
            }
            if (tetherState.lastState != IControlsTethering.STATE_TETHERED) {
                Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
            if (!tetherState.isCurrentlyServing()) {
                Log.e(TAG, "Tried to untether an inactive iface :" + iface + ", ignoring");
                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
            }
            tetherState.stateMachine.sendMessage(
@@ -565,6 +591,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    // TODO: Figure out how to update for local hotspot mode interfaces.
    private void sendTetherStateChangedBroadcast() {
        if (!getConnectivityManager().isTetheringSupported()) return;

@@ -728,7 +755,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                mRndisEnabled = rndisEnabled;
                // start tethering if we have a request pending
                if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
                    tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                    tetherMatchingInterfaces(
                            IControlsTethering.STATE_TETHERED,
                            ConnectivityManager.TETHERING_USB);
                }
                mUsbTetherRequested = false;
            }
@@ -743,9 +772,11 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                        break;
                    case WifiManager.WIFI_AP_STATE_ENABLED:
                        // When the AP comes up and we've been requested to tether it, do so.
                        if (mWifiTetherRequested) {
                            tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
                        }
                        // Otherwise, assume it's a local-only hotspot request.
                        final int state = mWifiTetherRequested
                                ? IControlsTethering.STATE_TETHERED
                                : IControlsTethering.STATE_LOCAL_HOTSPOT;
                        tetherMatchingInterfaces(state, ConnectivityManager.TETHERING_WIFI);
                        break;
                    case WifiManager.WIFI_AP_STATE_DISABLED:
                    case WifiManager.WIFI_AP_STATE_DISABLING:
@@ -775,8 +806,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
        if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");
    // TODO: Consider renaming to something more accurate in its description.
    // This method:
    //     - allows requesting either tethering or local hotspot serving states
    //     - handles both enabling and disabling serving states
    //     - only tethers the first matching interface in listInterfaces()
    //       order of a given type
    private void tetherMatchingInterfaces(int requestedState, int interfaceType) {
        if (VDBG) {
            Log.d(TAG, "tetherMatchingInterfaces(" + requestedState + ", " + interfaceType + ")");
        }

        String[] ifaces = null;
        try {
@@ -799,7 +838,20 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            return;
        }

        int result = (enable ? tether(chosenIface) : untether(chosenIface));
        final int result;
        switch (requestedState) {
            case IControlsTethering.STATE_UNAVAILABLE:
            case IControlsTethering.STATE_AVAILABLE:
                result = untether(chosenIface);
                break;
            case IControlsTethering.STATE_TETHERED:
            case IControlsTethering.STATE_LOCAL_HOTSPOT:
                result = tether(chosenIface, requestedState);
                break;
            default:
                Log.wtf(TAG, "Unknown interface state: " + requestedState);
                return;
        }
        if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
            Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
            return;
@@ -844,7 +896,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                if (mRndisEnabled) {
                    final long ident = Binder.clearCallingIdentity();
                    try {
                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                        tetherMatchingInterfaces(IControlsTethering.STATE_TETHERED,
                                ConnectivityManager.TETHERING_USB);
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
@@ -855,7 +908,8 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            } else {
                final long ident = Binder.clearCallingIdentity();
                try {
                    tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
                    tetherMatchingInterfaces(IControlsTethering.STATE_AVAILABLE,
                            ConnectivityManager.TETHERING_USB);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
@@ -919,6 +973,14 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    private boolean upstreamWanted() {
        if (!mForwardedDownstreams.isEmpty()) return true;

        synchronized (mPublicSync) {
            return mUsbTetherRequested || mWifiTetherRequested;
        }
    }

    // Needed because the canonical source of upstream truth is just the
    // upstream interface name, |mCurrentUpstreamIface|.  This is ripe for
    // future simplification, once the upstream Network is canonical.
@@ -935,10 +997,10 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering

    class TetherMasterSM extends StateMachine {
        private static final int BASE_MASTER                    = Protocol.BASE_TETHERING;
        // an interface SM has requested Tethering
        static final int CMD_TETHER_MODE_REQUESTED              = BASE_MASTER + 1;
        // an interface SM has unrequested Tethering
        static final int CMD_TETHER_MODE_UNREQUESTED            = BASE_MASTER + 2;
        // an interface SM has requested Tethering/Local Hotspot
        static final int EVENT_IFACE_SERVING_STATE_ACTIVE       = BASE_MASTER + 1;
        // an interface SM has unrequested Tethering/Local Hotspot
        static final int EVENT_IFACE_SERVING_STATE_INACTIVE     = BASE_MASTER + 2;
        // upstream connection change - do the right thing
        static final int CMD_UPSTREAM_CHANGED                   = BASE_MASTER + 3;
        // we don't have a valid upstream conn, check again after a delay
@@ -1023,7 +1085,9 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                    transitionTo(mSetIpForwardingEnabledErrorState);
                    return false;
                }
                // TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
                try {
                    // TODO: Find a more accurate method name (startDHCPv4()?).
                    mNMService.startTethering(cfg.dhcpRanges);
                } catch (Exception e) {
                    try {
@@ -1325,26 +1389,41 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            }
        }

        private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) {
            if (mNotifyList.indexOf(who) < 0) {
                mNotifyList.add(who);
                mIPv6TetheringCoordinator.addActiveDownstream(who, mode);
            }

            if (mode == IControlsTethering.STATE_TETHERED) {
                mForwardedDownstreams.add(who);
            } else {
                mForwardedDownstreams.remove(who);
            }
        }

        private void handleInterfaceServingStateInactive(TetherInterfaceStateMachine who) {
            mNotifyList.remove(who);
            mIPv6TetheringCoordinator.removeActiveDownstream(who);
            mForwardedDownstreams.remove(who);
        }

        class InitialState extends TetherMasterUtilState {
            @Override
            public boolean processMessage(Message message) {
                maybeLogMessage(this, message.what);
                boolean retValue = true;
                switch (message.what) {
                    case CMD_TETHER_MODE_REQUESTED:
                    case EVENT_IFACE_SERVING_STATE_ACTIVE:
                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                        if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
                        if (mNotifyList.indexOf(who) < 0) {
                            mNotifyList.add(who);
                            mIPv6TetheringCoordinator.addActiveDownstream(who);
                        }
                        handleInterfaceServingStateActive(message.arg1, who);
                        transitionTo(mTetherModeAliveState);
                        break;
                    case CMD_TETHER_MODE_UNREQUESTED:
                    case EVENT_IFACE_SERVING_STATE_INACTIVE:
                        who = (TetherInterfaceStateMachine)message.obj;
                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
                        mNotifyList.remove(who);
                        mIPv6TetheringCoordinator.removeActiveDownstream(who);
                        handleInterfaceServingStateInactive(who);
                        break;
                    default:
                        retValue = false;
@@ -1356,6 +1435,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering

        class TetherModeAliveState extends TetherMasterUtilState {
            final SimChangeListener simChange = new SimChangeListener(mContext);
            boolean mUpstreamWanted = false;
            boolean mTryCell = true;

            @Override
@@ -1366,10 +1446,12 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                mUpstreamNetworkMonitor.start();
                mOffloadController.start();

                // Better try something first pass or crazy tests cases will fail.
                if (upstreamWanted()) {
                    mUpstreamWanted = true;
                    chooseUpstreamType(true);
                    mTryCell = false;
                }
            }

            @Override
            public void exit() {
@@ -1381,27 +1463,36 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                handleNewUpstreamNetworkState(null);
            }

            private boolean updateUpstreamWanted() {
                final boolean previousUpstreamWanted = mUpstreamWanted;
                mUpstreamWanted = upstreamWanted();
                return previousUpstreamWanted;
            }

            @Override
            public boolean processMessage(Message message) {
                maybeLogMessage(this, message.what);
                boolean retValue = true;
                switch (message.what) {
                    case CMD_TETHER_MODE_REQUESTED: {
                    case EVENT_IFACE_SERVING_STATE_ACTIVE: {
                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                        if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
                        if (mNotifyList.indexOf(who) < 0) {
                            mNotifyList.add(who);
                            mIPv6TetheringCoordinator.addActiveDownstream(who);
                        }
                        handleInterfaceServingStateActive(message.arg1, who);
                        who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                                mCurrentUpstreamIface);
                        // If there has been a change and an upstream is now
                        // desired, kick off the selection process.
                        final boolean previousUpstreamWanted = updateUpstreamWanted();
                        if (!previousUpstreamWanted && mUpstreamWanted) {
                            chooseUpstreamType(true);
                        }
                        break;
                    }
                    case CMD_TETHER_MODE_UNREQUESTED: {
                    case EVENT_IFACE_SERVING_STATE_INACTIVE: {
                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
                        if (mNotifyList.remove(who)) {
                            if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
                        handleInterfaceServingStateInactive(who);

                        if (mNotifyList.isEmpty()) {
                            turnOffMasterTetherSettings(); // transitions appropriately
                        } else {
@@ -1413,22 +1504,33 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                                }
                            }
                        }
                        } else {
                           Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who);
                        // If there has been a change and an upstream is no
                        // longer desired, release any mobile requests.
                        final boolean previousUpstreamWanted = updateUpstreamWanted();
                        if (previousUpstreamWanted && !mUpstreamWanted) {
                            mUpstreamNetworkMonitor.releaseMobileNetworkRequest();
                        }
                        mIPv6TetheringCoordinator.removeActiveDownstream(who);
                        break;
                    }
                    case CMD_UPSTREAM_CHANGED:
                        updateUpstreamWanted();
                        if (!mUpstreamWanted) break;

                        // Need to try DUN immediately if Wi-Fi goes down.
                        chooseUpstreamType(true);
                        mTryCell = false;
                        break;
                    case CMD_RETRY_UPSTREAM:
                        updateUpstreamWanted();
                        if (!mUpstreamWanted) break;

                        chooseUpstreamType(mTryCell);
                        mTryCell = !mTryCell;
                        break;
                    case EVENT_UPSTREAM_CALLBACK: {
                        updateUpstreamWanted();
                        if (!mUpstreamWanted) break;

                        final NetworkState ns = (NetworkState) message.obj;

                        if (ns == null || !pertainsToCurrentUpstream(ns)) {
@@ -1490,7 +1592,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
            public boolean processMessage(Message message) {
                boolean retValue = true;
                switch (message.what) {
                    case CMD_TETHER_MODE_REQUESTED:
                    case EVENT_IFACE_SERVING_STATE_ACTIVE:
                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                        who.sendMessage(mErrorNotification);
                        break;
@@ -1604,12 +1706,16 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
                    case IControlsTethering.STATE_TETHERED:
                        pw.print("TetheredState");
                        break;
                    case IControlsTethering.STATE_LOCAL_HOTSPOT:
                        pw.print("LocalHotspotState");
                        break;
                    default:
                        pw.print("UnknownState");
                        break;
                }
                pw.println(" - lastError = " + tetherState.lastError);
            }
            pw.println("Upstream wanted: " + upstreamWanted());
            pw.decreaseIndent();
        }
        pw.decreaseIndent();
@@ -1648,15 +1754,21 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        if (error == ConnectivityManager.TETHER_ERROR_MASTER_ERROR) {
            mTetherMasterSM.sendMessage(TetherMasterSM.CMD_CLEAR_ERROR, who);
        }
        int which;
        switch (state) {
            case IControlsTethering.STATE_UNAVAILABLE:
            case IControlsTethering.STATE_AVAILABLE:
                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
                which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
                break;
            case IControlsTethering.STATE_TETHERED:
                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
            case IControlsTethering.STATE_LOCAL_HOTSPOT:
                which = TetherMasterSM.EVENT_IFACE_SERVING_STATE_ACTIVE;
                break;
            default:
                Log.wtf(TAG, "Unknown interface state: " + state);
                return;
        }
        mTetherMasterSM.sendMessage(which, state, 0, who);
        sendTetherStateChangedBroadcast();
    }

+1 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ public interface IControlsTethering {
    public final int STATE_UNAVAILABLE = 0;
    public final int STATE_AVAILABLE = 1;
    public final int STATE_TETHERED = 2;
    public final int STATE_LOCAL_HOTSPOT = 3;

    /**
     * Notify that |who| has changed its tethering state.  This may be called from any thread.
+106 −9

File changed.

Preview size limit exceeded, changes collapsed.

+18 −3
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
import java.util.Random;


/**
@@ -66,10 +67,17 @@ public class IPv6TetheringInterfaceServices {
    }

    public boolean start() {
        // TODO: Refactor for testability (perhaps passing an android.system.Os
        // instance and calling getifaddrs() directly).
        try {
            mNetworkInterface = NetworkInterface.getByName(mIfName);
        } catch (SocketException e) {
            Log.e(TAG, "Failed to find NetworkInterface for " + mIfName, e);
            Log.e(TAG, "Error looking up NetworkInterfaces for " + mIfName, e);
            stop();
            return false;
        }
        if (mNetworkInterface == null) {
            Log.e(TAG, "Failed to find NetworkInterface for " + mIfName);
            stop();
            return false;
        }
@@ -267,10 +275,10 @@ public class IPv6TetheringInterfaceServices {
        return localRoutes;
    }

    // Given a prefix like 2001:db8::/64 return 2001:db8::1.
    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
    private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
        final byte[] dnsBytes = localPrefix.getRawAddress();
        dnsBytes[dnsBytes.length - 1] = 0x1;
        dnsBytes[dnsBytes.length - 1] = getRandomNonZeroByte();
        try {
            return Inet6Address.getByAddress(null, dnsBytes, 0);
        } catch (UnknownHostException e) {
@@ -278,4 +286,11 @@ public class IPv6TetheringInterfaceServices {
            return null;
        }
    }

    private static byte getRandomNonZeroByte() {
        final byte random = (byte) (new Random()).nextInt();
        // Don't pick the subnet-router anycast address, since that might be
        // in use on the upstream already.
        return (random != 0) ? random : 0x1;
    }
}
+95 −35

File changed.

Preview size limit exceeded, changes collapsed.

Loading