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