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

Commit 8df5bd10 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Extract UpstreamNetworkMonitor to its own file"

parents 37727596 9bba340f
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);
    }
}