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