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

Commit 9bba340f authored by Erik Kline's avatar Erik Kline
Browse files

Extract UpstreamNetworkMonitor to its own file

Test: as follows:
    - built (bullhead)
    - flashed
    - booted
    - runtest framworks-net passes
Bug: 32163131

Change-Id: I87ff041e008e45065c9722de8130df53684fb2a9
parent fe4d2578
Loading
Loading
Loading
Loading
+1 −244
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
@@ -73,6 +72,7 @@ import com.android.server.connectivity.tethering.IControlsTethering;
import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
import com.android.server.connectivity.tethering.IPv6TetheringInterfaceServices;
import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
import com.android.server.net.BaseNetworkObserver;

import java.io.FileDescriptor;
@@ -1031,249 +1031,6 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering
        }
    }

    /**
     * A class to centralize all the network and link properties information
     * pertaining to the current and any potential upstream network.
     *
     * Calling #start() registers two callbacks: one to track the system default
     * network and a second to specifically observe TYPE_MOBILE_DUN networks.
     *
     * The methods and data members of this class are only to be accessed and
     * modified from the tethering master state machine thread. Any other
     * access semantics would necessitate the addition of locking.
     *
     * TODO: Investigate whether more "upstream-specific" logic/functionality
     * could/should be moved here.
     */
    public class UpstreamNetworkMonitor {
        public static final int EVENT_ON_AVAILABLE      = 1;
        public static final int EVENT_ON_CAPABILITIES   = 2;
        public static final int EVENT_ON_LINKPROPERTIES = 3;
        public static final int EVENT_ON_LOST           = 4;

        private final Context mContext;
        private final StateMachine mTarget;
        private final int mWhat;
        private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
        private ConnectivityManager mCM;
        private NetworkCallback mDefaultNetworkCallback;
        private NetworkCallback mDunTetheringCallback;
        private NetworkCallback mMobileNetworkCallback;
        private boolean mDunRequired;

        public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
            mContext = ctx;
            mTarget = tgt;
            mWhat = what;
        }

        public void start() {
            stop();

            mDefaultNetworkCallback = new UpstreamNetworkCallback();
            cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);

            final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                    .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                    .build();
            mDunTetheringCallback = new UpstreamNetworkCallback();
            cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
        }

        public void stop() {
            releaseMobileNetworkRequest();

            releaseCallback(mDefaultNetworkCallback);
            mDefaultNetworkCallback = null;

            releaseCallback(mDunTetheringCallback);
            mDunTetheringCallback = null;

            mNetworkMap.clear();
        }

        public void mobileUpstreamRequiresDun(boolean dunRequired) {
            final boolean valueChanged = (mDunRequired != dunRequired);
            mDunRequired = dunRequired;
            if (valueChanged && mobileNetworkRequested()) {
                releaseMobileNetworkRequest();
                registerMobileNetworkRequest();
            }
        }

        public boolean mobileNetworkRequested() {
            return (mMobileNetworkCallback != null);
        }

        public void registerMobileNetworkRequest() {
            if (mMobileNetworkCallback != null) return;

            final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
            if (mDunRequired) {
                builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                       .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
            } else {
                builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
            }
            final NetworkRequest mobileUpstreamRequest = builder.build();

            // The existing default network and DUN callbacks will be notified.
            // Therefore, to avoid duplicate notifications, we only register a no-op.
            mMobileNetworkCallback = new NetworkCallback();

            // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
            // moderate callback time (once timeout callbacks are implemented). This might
            // be useful for updating some UI. Additionally, we should definitely log a
            // message to aid in any subsequent debugging
            if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);

            cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
        }

        public void releaseMobileNetworkRequest() {
            if (mMobileNetworkCallback == null) return;

            cm().unregisterNetworkCallback(mMobileNetworkCallback);
            mMobileNetworkCallback = null;
        }

        public NetworkState lookup(Network network) {
            return (network != null) ? mNetworkMap.get(network) : null;
        }

        private void handleAvailable(Network network) {
            if (VDBG) {
                Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
            }
            if (!mNetworkMap.containsKey(network)) {
                mNetworkMap.put(network,
                        new NetworkState(null, null, null, network, null, null));
            }

            final ConnectivityManager cm = cm();

            if (mDefaultNetworkCallback != null) {
                cm.requestNetworkCapabilities(mDefaultNetworkCallback);
                cm.requestLinkProperties(mDefaultNetworkCallback);
            }

            // Requesting updates for mDunTetheringCallback is not
            // necessary. Because it's a listen, it will already have
            // heard all NetworkCapabilities and LinkProperties updates
            // since UpstreamNetworkMonitor was started. Because we
            // start UpstreamNetworkMonitor before chooseUpstreamType()
            // is ever invoked (it can register a DUN request) this is
            // mostly safe. However, if a DUN network is already up for
            // some reason (unlikely, because DUN is restricted and,
            // unless the DUN network is shared with another APN, only
            // the system can request it and this is the only part of
            // the system that requests it) we won't know its
            // LinkProperties or NetworkCapabilities.

            notifyTarget(EVENT_ON_AVAILABLE, network);
        }

        private void handleNetCap(Network network, NetworkCapabilities newNc) {
            if (!mNetworkMap.containsKey(network)) {
                // Ignore updates for networks for which we have not yet
                // received onAvailable() - which should never happen -
                // or for which we have already received onLost().
                return;
            }
            if (VDBG) {
                Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
                        network, newNc));
            }

            final NetworkState prev = mNetworkMap.get(network);
            mNetworkMap.put(network,
                    new NetworkState(null, prev.linkProperties, newNc,
                                     network, null, null));
            notifyTarget(EVENT_ON_CAPABILITIES, network);
        }

        private void handleLinkProp(Network network, LinkProperties newLp) {
            if (!mNetworkMap.containsKey(network)) {
                // Ignore updates for networks for which we have not yet
                // received onAvailable() - which should never happen -
                // or for which we have already received onLost().
                return;
            }
            if (VDBG) {
                Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
                        network, newLp));
            }

            final NetworkState prev = mNetworkMap.get(network);
            mNetworkMap.put(network,
                    new NetworkState(null, newLp, prev.networkCapabilities,
                                     network, null, null));
            notifyTarget(EVENT_ON_LINKPROPERTIES, network);
        }

        private void handleLost(Network network) {
            if (!mNetworkMap.containsKey(network)) {
                // Ignore updates for networks for which we have not yet
                // received onAvailable() - which should never happen -
                // or for which we have already received onLost().
                return;
            }
            if (VDBG) {
                Log.d(TAG, "EVENT_ON_LOST for " + network);
            }
            notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
        }

        // Fetch (and cache) a ConnectivityManager only if and when we need one.
        private ConnectivityManager cm() {
            if (mCM == null) {
                mCM = mContext.getSystemService(ConnectivityManager.class);
            }
            return mCM;
        }

        /**
         * A NetworkCallback class that relays information of interest to the
         * tethering master state machine thread for subsequent processing.
         */
        private class UpstreamNetworkCallback extends NetworkCallback {
            @Override
            public void onAvailable(Network network) {
                mTarget.getHandler().post(() -> handleAvailable(network));
            }

            @Override
            public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
                mTarget.getHandler().post(() -> handleNetCap(network, newNc));
            }

            @Override
            public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
                mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
            }

            @Override
            public void onLost(Network network) {
                mTarget.getHandler().post(() -> handleLost(network));
            }
        }

        private void releaseCallback(NetworkCallback cb) {
            if (cb != null) cm().unregisterNetworkCallback(cb);
        }

        private void notifyTarget(int which, Network network) {
            notifyTarget(which, mNetworkMap.get(network));
        }

        private void notifyTarget(int which, NetworkState netstate) {
            mTarget.sendMessage(mWhat, which, 0, netstate);
        }
    }

    // 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.
+280 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.NetworkState;
import android.util.Log;

import com.android.internal.util.StateMachine;

import java.util.HashMap;


/**
 * A class to centralize all the network and link properties information
 * pertaining to the current and any potential upstream network.
 *
 * Calling #start() registers two callbacks: one to track the system default
 * network and a second to specifically observe TYPE_MOBILE_DUN networks.
 *
 * The methods and data members of this class are only to be accessed and
 * modified from the tethering master state machine thread. Any other
 * access semantics would necessitate the addition of locking.
 *
 * TODO: Move upstream selection logic here.
 *
 * @hide
 */
public class UpstreamNetworkMonitor {
    private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
    private static final boolean DBG = false;
    private static final boolean VDBG = false;

    public static final int EVENT_ON_AVAILABLE      = 1;
    public static final int EVENT_ON_CAPABILITIES   = 2;
    public static final int EVENT_ON_LINKPROPERTIES = 3;
    public static final int EVENT_ON_LOST           = 4;

    private final Context mContext;
    private final StateMachine mTarget;
    private final int mWhat;
    private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
    private ConnectivityManager mCM;
    private NetworkCallback mDefaultNetworkCallback;
    private NetworkCallback mDunTetheringCallback;
    private NetworkCallback mMobileNetworkCallback;
    private boolean mDunRequired;

    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
        mContext = ctx;
        mTarget = tgt;
        mWhat = what;
    }

    public void start() {
        stop();

        mDefaultNetworkCallback = new UpstreamNetworkCallback();
        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback);

        final NetworkRequest dunTetheringRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                .build();
        mDunTetheringCallback = new UpstreamNetworkCallback();
        cm().registerNetworkCallback(dunTetheringRequest, mDunTetheringCallback);
    }

    public void stop() {
        releaseMobileNetworkRequest();

        releaseCallback(mDefaultNetworkCallback);
        mDefaultNetworkCallback = null;

        releaseCallback(mDunTetheringCallback);
        mDunTetheringCallback = null;

        mNetworkMap.clear();
    }

    public void mobileUpstreamRequiresDun(boolean dunRequired) {
        final boolean valueChanged = (mDunRequired != dunRequired);
        mDunRequired = dunRequired;
        if (valueChanged && mobileNetworkRequested()) {
            releaseMobileNetworkRequest();
            registerMobileNetworkRequest();
        }
    }

    public boolean mobileNetworkRequested() {
        return (mMobileNetworkCallback != null);
    }

    public void registerMobileNetworkRequest() {
        if (mMobileNetworkCallback != null) return;

        final NetworkRequest.Builder builder = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
        if (mDunRequired) {
            builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                   .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
        } else {
            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
        }
        final NetworkRequest mobileUpstreamRequest = builder.build();

        // The existing default network and DUN callbacks will be notified.
        // Therefore, to avoid duplicate notifications, we only register a no-op.
        mMobileNetworkCallback = new NetworkCallback();

        // TODO: Change the timeout from 0 (no onUnavailable callback) to use some
        // moderate callback time (once timeout callbacks are implemented). This might
        // be useful for updating some UI. Additionally, we should definitely log a
        // message to aid in any subsequent debugging
        if (DBG) Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);

        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback);
    }

    public void releaseMobileNetworkRequest() {
        if (mMobileNetworkCallback == null) return;

        cm().unregisterNetworkCallback(mMobileNetworkCallback);
        mMobileNetworkCallback = null;
    }

    public NetworkState lookup(Network network) {
        return (network != null) ? mNetworkMap.get(network) : null;
    }

    private void handleAvailable(Network network) {
        if (VDBG) {
            Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
        }
        if (!mNetworkMap.containsKey(network)) {
            mNetworkMap.put(network,
                    new NetworkState(null, null, null, network, null, null));
        }

        final ConnectivityManager cm = cm();

        if (mDefaultNetworkCallback != null) {
            cm.requestNetworkCapabilities(mDefaultNetworkCallback);
            cm.requestLinkProperties(mDefaultNetworkCallback);
        }

        // Requesting updates for mDunTetheringCallback is not
        // necessary. Because it's a listen, it will already have
        // heard all NetworkCapabilities and LinkProperties updates
        // since UpstreamNetworkMonitor was started. Because we
        // start UpstreamNetworkMonitor before chooseUpstreamType()
        // is ever invoked (it can register a DUN request) this is
        // mostly safe. However, if a DUN network is already up for
        // some reason (unlikely, because DUN is restricted and,
        // unless the DUN network is shared with another APN, only
        // the system can request it and this is the only part of
        // the system that requests it) we won't know its
        // LinkProperties or NetworkCapabilities.

        notifyTarget(EVENT_ON_AVAILABLE, network);
    }

    private void handleNetCap(Network network, NetworkCapabilities newNc) {
        if (!mNetworkMap.containsKey(network)) {
            // Ignore updates for networks for which we have not yet
            // received onAvailable() - which should never happen -
            // or for which we have already received onLost().
            return;
        }
        if (VDBG) {
            Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
                    network, newNc));
        }

        final NetworkState prev = mNetworkMap.get(network);
        mNetworkMap.put(network,
                new NetworkState(null, prev.linkProperties, newNc,
                                 network, null, null));
        notifyTarget(EVENT_ON_CAPABILITIES, network);
    }

    private void handleLinkProp(Network network, LinkProperties newLp) {
        if (!mNetworkMap.containsKey(network)) {
            // Ignore updates for networks for which we have not yet
            // received onAvailable() - which should never happen -
            // or for which we have already received onLost().
            return;
        }
        if (VDBG) {
            Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
                    network, newLp));
        }

        final NetworkState prev = mNetworkMap.get(network);
        mNetworkMap.put(network,
                new NetworkState(null, newLp, prev.networkCapabilities,
                                 network, null, null));
        notifyTarget(EVENT_ON_LINKPROPERTIES, network);
    }

    private void handleLost(Network network) {
        if (!mNetworkMap.containsKey(network)) {
            // Ignore updates for networks for which we have not yet
            // received onAvailable() - which should never happen -
            // or for which we have already received onLost().
            return;
        }
        if (VDBG) {
            Log.d(TAG, "EVENT_ON_LOST for " + network);
        }
        notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
    }

    // Fetch (and cache) a ConnectivityManager only if and when we need one.
    private ConnectivityManager cm() {
        if (mCM == null) {
            mCM = mContext.getSystemService(ConnectivityManager.class);
        }
        return mCM;
    }

    /**
     * A NetworkCallback class that relays information of interest to the
     * tethering master state machine thread for subsequent processing.
     */
    private class UpstreamNetworkCallback extends NetworkCallback {
        @Override
        public void onAvailable(Network network) {
            mTarget.getHandler().post(() -> handleAvailable(network));
        }

        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
            mTarget.getHandler().post(() -> handleNetCap(network, newNc));
        }

        @Override
        public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
            mTarget.getHandler().post(() -> handleLinkProp(network, newLp));
        }

        @Override
        public void onLost(Network network) {
            mTarget.getHandler().post(() -> handleLost(network));
        }
    }

    private void releaseCallback(NetworkCallback cb) {
        if (cb != null) cm().unregisterNetworkCallback(cb);
    }

    private void notifyTarget(int which, Network network) {
        notifyTarget(which, mNetworkMap.get(network));
    }

    private void notifyTarget(int which, NetworkState netstate) {
        mTarget.sendMessage(mWhat, which, 0, netstate);
    }
}