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