UpstreamNetworkMonitor.java revision 3760999e347ae1cfa25087451638de8ddc79c9f6
19bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline/*
29bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * Copyright (C) 2017 The Android Open Source Project
39bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
49bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * Licensed under the Apache License, Version 2.0 (the "License");
59bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * you may not use this file except in compliance with the License.
69bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * You may obtain a copy of the License at
79bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
89bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *      http://www.apache.org/licenses/LICENSE-2.0
99bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
109bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * Unless required by applicable law or agreed to in writing, software
119bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * distributed under the License is distributed on an "AS IS" BASIS,
129bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * See the License for the specific language governing permissions and
149bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * limitations under the License.
159bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline */
169bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
179bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klinepackage com.android.server.connectivity.tethering;
189bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
193760999e347ae1cfa25087451638de8ddc79c9f6Erik Klineimport static android.net.ConnectivityManager.getNetworkTypeName;
203760999e347ae1cfa25087451638de8ddc79c9f6Erik Klineimport static android.net.ConnectivityManager.TYPE_NONE;
21dd8e8911c69dd68d268f892373b174bc7b422db8Erik Klineimport static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
22dd8e8911c69dd68d268f892373b174bc7b422db8Erik Klineimport static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
23dd8e8911c69dd68d268f892373b174bc7b422db8Erik Kline
249bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.content.Context;
25b583b03675492280ab87f1eff30c05709cafba23Erik Klineimport android.os.Handler;
26b583b03675492280ab87f1eff30c05709cafba23Erik Klineimport android.os.Looper;
279bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.ConnectivityManager;
289bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.ConnectivityManager.NetworkCallback;
299bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.LinkProperties;
309bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.Network;
319bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.NetworkCapabilities;
329bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.NetworkRequest;
339bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.net.NetworkState;
347747fd4ce055e8e1f6381062a58b867728f57d85Erik Klineimport android.net.util.SharedLog;
359bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport android.util.Log;
369bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
37885a9098cf19eaee5a164c5ca771aec5926f2efdErik Klineimport com.android.internal.annotations.VisibleForTesting;
389bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport com.android.internal.util.StateMachine;
399bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
409bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klineimport java.util.HashMap;
419bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
429bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
439bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline/**
449bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * A class to centralize all the network and link properties information
459bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * pertaining to the current and any potential upstream network.
469bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
479bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * Calling #start() registers two callbacks: one to track the system default
48d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * network and a second to observe all networks.  The latter is necessary
49d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * while the expression of preferred upstreams remains a list of legacy
50d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * connectivity types.  In future, this can be revisited.
519bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
529bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * The methods and data members of this class are only to be accessed and
539bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * modified from the tethering master state machine thread. Any other
549bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * access semantics would necessitate the addition of locking.
559bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
569bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * TODO: Move upstream selection logic here.
579bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline *
58d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * All callback methods are run on the same thread as the specified target
59d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * state machine.  This class does not require locking when accessed from this
60d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline * thread.  Access from other threads is not advised.
61d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline *
629bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline * @hide
639bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline */
649bba340fd4edd5aea91d612a332f964b0d63cf34Erik Klinepublic class UpstreamNetworkMonitor {
659bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
669bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private static final boolean DBG = false;
679bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private static final boolean VDBG = false;
689bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
699bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public static final int EVENT_ON_AVAILABLE      = 1;
709bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public static final int EVENT_ON_CAPABILITIES   = 2;
719bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public static final int EVENT_ON_LINKPROPERTIES = 3;
729bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public static final int EVENT_ON_LOST           = 4;
739bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
7435bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline    private static final int CALLBACK_LISTEN_ALL = 1;
7535bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline    private static final int CALLBACK_TRACK_DEFAULT = 2;
7635bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline    private static final int CALLBACK_MOBILE_REQUEST = 3;
77d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
789bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private final Context mContext;
797747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline    private final SharedLog mLog;
809bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private final StateMachine mTarget;
81b583b03675492280ab87f1eff30c05709cafba23Erik Kline    private final Handler mHandler;
829bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private final int mWhat;
839bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
849bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private ConnectivityManager mCM;
85d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline    private NetworkCallback mListenAllCallback;
869bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private NetworkCallback mDefaultNetworkCallback;
879bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private NetworkCallback mMobileNetworkCallback;
889bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private boolean mDunRequired;
89d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline    private Network mCurrentDefault;
909bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
917747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) {
929bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mContext = ctx;
939bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mTarget = tgt;
94b583b03675492280ab87f1eff30c05709cafba23Erik Kline        mHandler = mTarget.getHandler();
959bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mWhat = what;
967747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline        mLog = log.forSubComponent(TAG);
979bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
989bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
99885a9098cf19eaee5a164c5ca771aec5926f2efdErik Kline    @VisibleForTesting
1007747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline    public UpstreamNetworkMonitor(
1017747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline            StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) {
1027747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline        this(null, tgt, what, log);
103885a9098cf19eaee5a164c5ca771aec5926f2efdErik Kline        mCM = cm;
104885a9098cf19eaee5a164c5ca771aec5926f2efdErik Kline    }
105885a9098cf19eaee5a164c5ca771aec5926f2efdErik Kline
1069bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public void start() {
1079bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        stop();
1089bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
109d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
110d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline                .clearCapabilities().build();
11135bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
112b583b03675492280ab87f1eff30c05709cafba23Erik Kline        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
1139bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
11435bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT);
115b583b03675492280ab87f1eff30c05709cafba23Erik Kline        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
1169bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1179bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1189bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public void stop() {
1199bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        releaseMobileNetworkRequest();
1209bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1219bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        releaseCallback(mDefaultNetworkCallback);
1229bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mDefaultNetworkCallback = null;
1239bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
124d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        releaseCallback(mListenAllCallback);
125d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        mListenAllCallback = null;
1269bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1279bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mNetworkMap.clear();
1289bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1299bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
130efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline    public void updateMobileRequiresDun(boolean dunRequired) {
1319bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        final boolean valueChanged = (mDunRequired != dunRequired);
1329bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mDunRequired = dunRequired;
1339bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (valueChanged && mobileNetworkRequested()) {
1349bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            releaseMobileNetworkRequest();
1359bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            registerMobileNetworkRequest();
1369bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
1379bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1389bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1399bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public boolean mobileNetworkRequested() {
1409bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        return (mMobileNetworkCallback != null);
1419bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1429bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1439bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public void registerMobileNetworkRequest() {
144efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline        if (mMobileNetworkCallback != null) {
1457747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline            mLog.e("registerMobileNetworkRequest() already registered");
146efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline            return;
147efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline        }
1489bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
14935bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        // The following use of the legacy type system cannot be removed until
15035bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        // after upstream selection no longer finds networks by legacy type.
15135bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        // See also http://b/34364553 .
15235bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
15335bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline
15435bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
15535bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
15635bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                .build();
1579bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1589bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        // The existing default network and DUN callbacks will be notified.
1599bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        // Therefore, to avoid duplicate notifications, we only register a no-op.
16035bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
1619bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
162efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline        // TODO: Change the timeout from 0 (no onUnavailable callback) to some
163efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline        // moderate callback timeout. This might be useful for updating some UI.
164efdd3f4ce04d3e1741831982ff86a58da6b542d3Erik Kline        // Additionally, we log a message to aid in any subsequent debugging.
1657747fd4ce055e8e1f6381062a58b867728f57d85Erik Kline        mLog.i("requesting mobile upstream network: " + mobileUpstreamRequest);
1669bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
167b583b03675492280ab87f1eff30c05709cafba23Erik Kline        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, legacyType, mHandler);
1689bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1699bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1709bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public void releaseMobileNetworkRequest() {
1719bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (mMobileNetworkCallback == null) return;
1729bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1739bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        cm().unregisterNetworkCallback(mMobileNetworkCallback);
1749bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mMobileNetworkCallback = null;
1759bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1769bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1779bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    public NetworkState lookup(Network network) {
1789bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        return (network != null) ? mNetworkMap.get(network) : null;
1799bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
1809bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
1813760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // So many TODOs here, but chief among them is: make this functionality an
1823760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // integral part of this class such that whenever a higher priority network
1833760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // becomes available and useful we (a) file a request to keep it up as
1843760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // necessary and (b) change all upstream tracking state accordingly (by
1853760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // passing LinkProperties up to Tethering).
1863760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    //
1873760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    // Next TODO: return NetworkState instead of just the type.
1883760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    public int selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
1893760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
1903760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                mNetworkMap.values(), preferredTypes);
1913760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
1923760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
1933760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
1943760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        switch (typeStatePair.type) {
1953760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            case TYPE_MOBILE_DUN:
1963760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            case TYPE_MOBILE_HIPRI:
1973760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                // If we're on DUN, put our own grab on it.
1983760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                registerMobileNetworkRequest();
1993760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                break;
2003760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            case TYPE_NONE:
2013760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                break;
2023760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            default:
2033760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                /* If we've found an active upstream connection that's not DUN/HIPRI
2043760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                 * we should stop any outstanding DUN/HIPRI start requests.
2053760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                 *
2063760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                 * If we found NONE we don't want to do this as we want any previous
2073760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                 * requests to keep trying to bring up something we can use.
2083760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                 */
2093760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                releaseMobileNetworkRequest();
2103760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                break;
2113760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        }
2123760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
2133760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        return typeStatePair.type;
2143760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    }
2153760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
216d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline    private void handleAvailable(int callbackType, Network network) {
217d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
218d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
2199bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (!mNetworkMap.containsKey(network)) {
2209bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            mNetworkMap.put(network,
2219bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline                    new NetworkState(null, null, null, network, null, null));
2229bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
2239bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
224d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // Always request whatever extra information we can, in case this
225d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // was already up when start() was called, in which case we would
226d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // not have been notified of any information that had not changed.
22735bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        switch (callbackType) {
22835bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline            case CALLBACK_LISTEN_ALL:
22935bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                break;
230465c46d555c867caf40333db197ef97cff75a754Erik Kline
23135bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline            case CALLBACK_TRACK_DEFAULT:
23232858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                if (mDefaultNetworkCallback == null) {
23332858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // The callback was unregistered in the interval between
234b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    // ConnectivityService enqueueing onAvailable() and our
235b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    // handling of it here on the mHandler thread.
236b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    //
23732858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // Clean-up of this network entry is deferred to the
23832858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // handling of onLost() by other callbacks.
239b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    //
240b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    // These request*() calls can be deleted post oag/339444.
24132858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    return;
24232858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                }
24335bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                mCurrentDefault = network;
24435bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                break;
245465c46d555c867caf40333db197ef97cff75a754Erik Kline
24635bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline            case CALLBACK_MOBILE_REQUEST:
24732858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                if (mMobileNetworkCallback == null) {
24832858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // The callback was unregistered in the interval between
249b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    // ConnectivityService enqueueing onAvailable() and our
250b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    // handling of it here on the mHandler thread.
251b583b03675492280ab87f1eff30c05709cafba23Erik Kline                    //
25232858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // Clean-up of this network entry is deferred to the
25332858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    // handling of onLost() by other callbacks.
25432858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                    return;
25532858e14caa09fbb3eb4081f114f2a99181c9643Erik Kline                }
25635bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline                break;
257d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        }
2589bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
259d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // Requesting updates for mListenAllCallback is not currently possible
260d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // because it's a "listen". Two possible solutions to getting updates
261d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // about networks without waiting for a change (which might never come)
262d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // are:
263d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //
264d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //     [1] extend request{NetworkCapabilities,LinkProperties}() to
265d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //         take a Network argument and have ConnectivityService do
266d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //         what's required (if the network satisfies the request)
267d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //
268d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //     [2] explicitly file a NetworkRequest for each connectivity type
269d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //         listed as a preferred upstream and wait for these callbacks
270d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //         to be notified (requires tracking many more callbacks).
271d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        //
272d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // Until this is addressed, networks that exist prior to the "listen"
273d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // registration and which do not subsequently change will not cause
274d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // us to learn their NetworkCapabilities nor their LinkProperties.
275d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
276d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // TODO: If sufficient information is available to select a more
277d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // preferable upstream, do so now and notify the target.
2789bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        notifyTarget(EVENT_ON_AVAILABLE, network);
2799bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
2809bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
2819bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private void handleNetCap(Network network, NetworkCapabilities newNc) {
282d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        final NetworkState prev = mNetworkMap.get(network);
283d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        if (prev == null || newNc.equals(prev.networkCapabilities)) {
284d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // Ignore notifications about networks for which we have not yet
285d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // received onAvailable() (should never happen) and any duplicate
286d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // notifications (e.g. matching more than one of our callbacks).
2879bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            return;
2889bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
289d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
2909bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (VDBG) {
2919bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
2929bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline                    network, newNc));
2939bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
2949bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
295d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        mNetworkMap.put(network, new NetworkState(
296d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline                null, prev.linkProperties, newNc, network, null, null));
297d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // TODO: If sufficient information is available to select a more
298d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // preferable upstream, do so now and notify the target.
2999bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        notifyTarget(EVENT_ON_CAPABILITIES, network);
3009bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3019bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3029bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private void handleLinkProp(Network network, LinkProperties newLp) {
303d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        final NetworkState prev = mNetworkMap.get(network);
304d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        if (prev == null || newLp.equals(prev.linkProperties)) {
305d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // Ignore notifications about networks for which we have not yet
306d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // received onAvailable() (should never happen) and any duplicate
307d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // notifications (e.g. matching more than one of our callbacks).
3089bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            return;
3099bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
310d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
3119bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (VDBG) {
3129bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
3139bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline                    network, newLp));
3149bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
3159bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
316d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        mNetworkMap.put(network, new NetworkState(
317d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline                null, newLp, prev.networkCapabilities, network, null, null));
318d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // TODO: If sufficient information is available to select a more
319d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // preferable upstream, do so now and notify the target.
3209bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        notifyTarget(EVENT_ON_LINKPROPERTIES, network);
3219bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3229bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
323d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline    private void handleLost(int callbackType, Network network) {
32435bf06c968ecbae502efd9cfb13fff9b7ddf9d6aErik Kline        if (callbackType == CALLBACK_TRACK_DEFAULT) {
325d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            mCurrentDefault = null;
326d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // Receiving onLost() for a default network does not necessarily
327d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // mean the network is gone.  We wait for a separate notification
328d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
329d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // clearing all state.
3309bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline            return;
3319bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
332d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
333d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        if (!mNetworkMap.containsKey(network)) {
334d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // Ignore loss of networks about which we had not previously
335d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // learned any information or for which we have already processed
336d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            // an onLost() notification.
337d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            return;
3389bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
339d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
340d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
341d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
342d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // TODO: If sufficient information is available to select a more
343d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // preferable upstream, do so now and notify the target.  Likewise,
344d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // if the current upstream network is gone, notify the target of the
345d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        // fact that we now have no upstream at all.
3469bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
3479bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3489bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3499bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    // Fetch (and cache) a ConnectivityManager only if and when we need one.
3509bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private ConnectivityManager cm() {
3519bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (mCM == null) {
352ea9cc488eb0f096c9fd402eff49e3d30f5b6de2eErik Kline            // MUST call the String variant to be able to write unittests.
353ea9cc488eb0f096c9fd402eff49e3d30f5b6de2eErik Kline            mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
3549bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
3559bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        return mCM;
3569bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3579bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3589bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    /**
359b583b03675492280ab87f1eff30c05709cafba23Erik Kline     * A NetworkCallback class that handles information of interest directly
360b583b03675492280ab87f1eff30c05709cafba23Erik Kline     * in the thread on which it is invoked. To avoid locking, this MUST be
361b583b03675492280ab87f1eff30c05709cafba23Erik Kline     * run on the same thread as the target state machine's handler.
3629bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline     */
3639bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private class UpstreamNetworkCallback extends NetworkCallback {
364d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        private final int mCallbackType;
365d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
366d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        UpstreamNetworkCallback(int callbackType) {
367d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline            mCallbackType = callbackType;
368d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline        }
369d2ec3919cb87931b2c143d0c1b98caf37039d882Erik Kline
3709bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        @Override
3719bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        public void onAvailable(Network network) {
372b583b03675492280ab87f1eff30c05709cafba23Erik Kline            handleAvailable(mCallbackType, network);
3739bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
3749bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3759bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        @Override
3769bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
377b583b03675492280ab87f1eff30c05709cafba23Erik Kline            handleNetCap(network, newNc);
3789bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
3799bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3809bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        @Override
3819bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
382b583b03675492280ab87f1eff30c05709cafba23Erik Kline            handleLinkProp(network, newLp);
3839bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        }
3849bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
385b583b03675492280ab87f1eff30c05709cafba23Erik Kline        // TODO: Handle onNetworkSuspended();
386b583b03675492280ab87f1eff30c05709cafba23Erik Kline        // TODO: Handle onNetworkResumed();
387b583b03675492280ab87f1eff30c05709cafba23Erik Kline
3889bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        @Override
3899bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        public void onLost(Network network) {
390b583b03675492280ab87f1eff30c05709cafba23Erik Kline            handleLost(mCallbackType, network);
391b583b03675492280ab87f1eff30c05709cafba23Erik Kline        }
3929bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3939bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3949bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private void releaseCallback(NetworkCallback cb) {
3959bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        if (cb != null) cm().unregisterNetworkCallback(cb);
3969bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
3979bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
3989bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private void notifyTarget(int which, Network network) {
3999bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        notifyTarget(which, mNetworkMap.get(network));
4009bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
4019bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline
4029bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    private void notifyTarget(int which, NetworkState netstate) {
4039bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline        mTarget.sendMessage(mWhat, which, 0, netstate);
4049bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline    }
4053760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4063760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    static private class TypeStatePair {
4073760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        public int type = TYPE_NONE;
4083760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        public NetworkState ns = null;
4093760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    }
4103760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4113760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    static private TypeStatePair findFirstAvailableUpstreamByType(
4123760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
4133760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        final TypeStatePair result = new TypeStatePair();
4143760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4153760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        for (int type : preferredTypes) {
4163760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            NetworkCapabilities nc;
4173760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            try {
4183760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                nc = ConnectivityManager.networkCapabilitiesForType(type);
4193760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            } catch (IllegalArgumentException iae) {
4203760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " +
4213760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                       ConnectivityManager.getNetworkTypeName(type));
4223760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                continue;
4233760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            }
4243760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4253760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            for (NetworkState value : netStates) {
4263760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
4273760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                    continue;
4283760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                }
4293760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4303760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                result.type = type;
4313760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                result.ns = value;
4323760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline                return result;
4333760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline            }
4343760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        }
4353760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline
4363760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline        return result;
4373760999e347ae1cfa25087451638de8ddc79c9f6Erik Kline    }
4389bba340fd4edd5aea91d612a332f964b0d63cf34Erik Kline}
439