UpstreamNetworkMonitor.java revision 465c46d555c867caf40333db197ef97cff75a754
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity.tethering;
18
19import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
20import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
21
22import android.content.Context;
23import android.os.Handler;
24import android.os.Looper;
25import android.net.ConnectivityManager;
26import android.net.ConnectivityManager.NetworkCallback;
27import android.net.LinkProperties;
28import android.net.Network;
29import android.net.NetworkCapabilities;
30import android.net.NetworkRequest;
31import android.net.NetworkState;
32import android.util.Log;
33
34import com.android.internal.annotations.VisibleForTesting;
35import com.android.internal.util.StateMachine;
36
37import java.util.HashMap;
38
39
40/**
41 * A class to centralize all the network and link properties information
42 * pertaining to the current and any potential upstream network.
43 *
44 * Calling #start() registers two callbacks: one to track the system default
45 * network and a second to observe all networks.  The latter is necessary
46 * while the expression of preferred upstreams remains a list of legacy
47 * connectivity types.  In future, this can be revisited.
48 *
49 * The methods and data members of this class are only to be accessed and
50 * modified from the tethering master state machine thread. Any other
51 * access semantics would necessitate the addition of locking.
52 *
53 * TODO: Move upstream selection logic here.
54 *
55 * All callback methods are run on the same thread as the specified target
56 * state machine.  This class does not require locking when accessed from this
57 * thread.  Access from other threads is not advised.
58 *
59 * @hide
60 */
61public class UpstreamNetworkMonitor {
62    private static final String TAG = UpstreamNetworkMonitor.class.getSimpleName();
63    private static final boolean DBG = false;
64    private static final boolean VDBG = false;
65
66    public static final int EVENT_ON_AVAILABLE      = 1;
67    public static final int EVENT_ON_CAPABILITIES   = 2;
68    public static final int EVENT_ON_LINKPROPERTIES = 3;
69    public static final int EVENT_ON_LOST           = 4;
70
71    private static final int CALLBACK_LISTEN_ALL = 1;
72    private static final int CALLBACK_TRACK_DEFAULT = 2;
73    private static final int CALLBACK_MOBILE_REQUEST = 3;
74
75    private final Context mContext;
76    private final StateMachine mTarget;
77    private final Handler mHandler;
78    private final int mWhat;
79    private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
80    private ConnectivityManager mCM;
81    private NetworkCallback mListenAllCallback;
82    private NetworkCallback mDefaultNetworkCallback;
83    private NetworkCallback mMobileNetworkCallback;
84    private boolean mDunRequired;
85    private Network mCurrentDefault;
86
87    public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what) {
88        mContext = ctx;
89        mTarget = tgt;
90        mHandler = mTarget.getHandler();
91        mWhat = what;
92    }
93
94    @VisibleForTesting
95    public UpstreamNetworkMonitor(StateMachine tgt, int what, ConnectivityManager cm) {
96        this(null, tgt, what);
97        mCM = cm;
98    }
99
100    public void start() {
101        stop();
102
103        final NetworkRequest listenAllRequest = new NetworkRequest.Builder()
104                .clearCapabilities().build();
105        mListenAllCallback = new UpstreamNetworkCallback(CALLBACK_LISTEN_ALL);
106        cm().registerNetworkCallback(listenAllRequest, mListenAllCallback, mHandler);
107
108        mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_TRACK_DEFAULT);
109        cm().registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
110    }
111
112    public void stop() {
113        releaseMobileNetworkRequest();
114
115        releaseCallback(mDefaultNetworkCallback);
116        mDefaultNetworkCallback = null;
117
118        releaseCallback(mListenAllCallback);
119        mListenAllCallback = null;
120
121        mNetworkMap.clear();
122    }
123
124    public void updateMobileRequiresDun(boolean dunRequired) {
125        final boolean valueChanged = (mDunRequired != dunRequired);
126        mDunRequired = dunRequired;
127        if (valueChanged && mobileNetworkRequested()) {
128            releaseMobileNetworkRequest();
129            registerMobileNetworkRequest();
130        }
131    }
132
133    public boolean mobileNetworkRequested() {
134        return (mMobileNetworkCallback != null);
135    }
136
137    public void registerMobileNetworkRequest() {
138        if (mMobileNetworkCallback != null) {
139            Log.e(TAG, "registerMobileNetworkRequest() already registered");
140            return;
141        }
142
143        // The following use of the legacy type system cannot be removed until
144        // after upstream selection no longer finds networks by legacy type.
145        // See also http://b/34364553 .
146        final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
147
148        final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
149                .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
150                .build();
151
152        // The existing default network and DUN callbacks will be notified.
153        // Therefore, to avoid duplicate notifications, we only register a no-op.
154        mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
155
156        // TODO: Change the timeout from 0 (no onUnavailable callback) to some
157        // moderate callback timeout. This might be useful for updating some UI.
158        // Additionally, we log a message to aid in any subsequent debugging.
159        Log.d(TAG, "requesting mobile upstream network: " + mobileUpstreamRequest);
160
161        cm().requestNetwork(mobileUpstreamRequest, mMobileNetworkCallback, 0, legacyType, mHandler);
162    }
163
164    public void releaseMobileNetworkRequest() {
165        if (mMobileNetworkCallback == null) return;
166
167        cm().unregisterNetworkCallback(mMobileNetworkCallback);
168        mMobileNetworkCallback = null;
169    }
170
171    public NetworkState lookup(Network network) {
172        return (network != null) ? mNetworkMap.get(network) : null;
173    }
174
175    private void handleAvailable(int callbackType, Network network) {
176        if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network);
177
178        if (!mNetworkMap.containsKey(network)) {
179            mNetworkMap.put(network,
180                    new NetworkState(null, null, null, network, null, null));
181        }
182
183        // Always request whatever extra information we can, in case this
184        // was already up when start() was called, in which case we would
185        // not have been notified of any information that had not changed.
186        switch (callbackType) {
187            case CALLBACK_LISTEN_ALL:
188                break;
189
190            case CALLBACK_TRACK_DEFAULT:
191                if (mDefaultNetworkCallback == null) {
192                    // The callback was unregistered in the interval between
193                    // ConnectivityService enqueueing onAvailable() and our
194                    // handling of it here on the mHandler thread.
195                    //
196                    // Clean-up of this network entry is deferred to the
197                    // handling of onLost() by other callbacks.
198                    //
199                    // These request*() calls can be deleted post oag/339444.
200                    return;
201                }
202                mCurrentDefault = network;
203                break;
204
205            case CALLBACK_MOBILE_REQUEST:
206                if (mMobileNetworkCallback == null) {
207                    // The callback was unregistered in the interval between
208                    // ConnectivityService enqueueing onAvailable() and our
209                    // handling of it here on the mHandler thread.
210                    //
211                    // Clean-up of this network entry is deferred to the
212                    // handling of onLost() by other callbacks.
213                    return;
214                }
215                break;
216        }
217
218        // Requesting updates for mListenAllCallback is not currently possible
219        // because it's a "listen". Two possible solutions to getting updates
220        // about networks without waiting for a change (which might never come)
221        // are:
222        //
223        //     [1] extend request{NetworkCapabilities,LinkProperties}() to
224        //         take a Network argument and have ConnectivityService do
225        //         what's required (if the network satisfies the request)
226        //
227        //     [2] explicitly file a NetworkRequest for each connectivity type
228        //         listed as a preferred upstream and wait for these callbacks
229        //         to be notified (requires tracking many more callbacks).
230        //
231        // Until this is addressed, networks that exist prior to the "listen"
232        // registration and which do not subsequently change will not cause
233        // us to learn their NetworkCapabilities nor their LinkProperties.
234
235        // TODO: If sufficient information is available to select a more
236        // preferable upstream, do so now and notify the target.
237        notifyTarget(EVENT_ON_AVAILABLE, network);
238    }
239
240    private void handleNetCap(Network network, NetworkCapabilities newNc) {
241        final NetworkState prev = mNetworkMap.get(network);
242        if (prev == null || newNc.equals(prev.networkCapabilities)) {
243            // Ignore notifications about networks for which we have not yet
244            // received onAvailable() (should never happen) and any duplicate
245            // notifications (e.g. matching more than one of our callbacks).
246            return;
247        }
248
249        if (VDBG) {
250            Log.d(TAG, String.format("EVENT_ON_CAPABILITIES for %s: %s",
251                    network, newNc));
252        }
253
254        mNetworkMap.put(network, new NetworkState(
255                null, prev.linkProperties, newNc, network, null, null));
256        // TODO: If sufficient information is available to select a more
257        // preferable upstream, do so now and notify the target.
258        notifyTarget(EVENT_ON_CAPABILITIES, network);
259    }
260
261    private void handleLinkProp(Network network, LinkProperties newLp) {
262        final NetworkState prev = mNetworkMap.get(network);
263        if (prev == null || newLp.equals(prev.linkProperties)) {
264            // Ignore notifications about networks for which we have not yet
265            // received onAvailable() (should never happen) and any duplicate
266            // notifications (e.g. matching more than one of our callbacks).
267            return;
268        }
269
270        if (VDBG) {
271            Log.d(TAG, String.format("EVENT_ON_LINKPROPERTIES for %s: %s",
272                    network, newLp));
273        }
274
275        mNetworkMap.put(network, new NetworkState(
276                null, newLp, prev.networkCapabilities, network, null, null));
277        // TODO: If sufficient information is available to select a more
278        // preferable upstream, do so now and notify the target.
279        notifyTarget(EVENT_ON_LINKPROPERTIES, network);
280    }
281
282    private void handleLost(int callbackType, Network network) {
283        if (callbackType == CALLBACK_TRACK_DEFAULT) {
284            mCurrentDefault = null;
285            // Receiving onLost() for a default network does not necessarily
286            // mean the network is gone.  We wait for a separate notification
287            // on either the LISTEN_ALL or MOBILE_REQUEST callbacks before
288            // clearing all state.
289            return;
290        }
291
292        if (!mNetworkMap.containsKey(network)) {
293            // Ignore loss of networks about which we had not previously
294            // learned any information or for which we have already processed
295            // an onLost() notification.
296            return;
297        }
298
299        if (VDBG) Log.d(TAG, "EVENT_ON_LOST for " + network);
300
301        // TODO: If sufficient information is available to select a more
302        // preferable upstream, do so now and notify the target.  Likewise,
303        // if the current upstream network is gone, notify the target of the
304        // fact that we now have no upstream at all.
305        notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
306    }
307
308    // Fetch (and cache) a ConnectivityManager only if and when we need one.
309    private ConnectivityManager cm() {
310        if (mCM == null) {
311            mCM = mContext.getSystemService(ConnectivityManager.class);
312        }
313        return mCM;
314    }
315
316    /**
317     * A NetworkCallback class that handles information of interest directly
318     * in the thread on which it is invoked. To avoid locking, this MUST be
319     * run on the same thread as the target state machine's handler.
320     */
321    private class UpstreamNetworkCallback extends NetworkCallback {
322        private final int mCallbackType;
323
324        UpstreamNetworkCallback(int callbackType) {
325            mCallbackType = callbackType;
326        }
327
328        @Override
329        public void onAvailable(Network network) {
330            checkExpectedThread();
331            handleAvailable(mCallbackType, network);
332        }
333
334        @Override
335        public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
336            checkExpectedThread();
337            handleNetCap(network, newNc);
338        }
339
340        @Override
341        public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
342            checkExpectedThread();
343            handleLinkProp(network, newLp);
344        }
345
346        // TODO: Handle onNetworkSuspended();
347        // TODO: Handle onNetworkResumed();
348
349        @Override
350        public void onLost(Network network) {
351            checkExpectedThread();
352            handleLost(mCallbackType, network);
353        }
354
355        private void checkExpectedThread() {
356            if (Looper.myLooper() != mHandler.getLooper()) {
357                Log.wtf(TAG, "Handling callback in unexpected thread.");
358            }
359        }
360    }
361
362    private void releaseCallback(NetworkCallback cb) {
363        if (cb != null) cm().unregisterNetworkCallback(cb);
364    }
365
366    private void notifyTarget(int which, Network network) {
367        notifyTarget(which, mNetworkMap.get(network));
368    }
369
370    private void notifyTarget(int which, NetworkState netstate) {
371        mTarget.sendMessage(mWhat, which, 0, netstate);
372    }
373}
374