1 /* 2 * Copyright (C) 2016 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; 18 19 import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; 20 import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; 21 22 import android.annotation.NonNull; 23 import android.annotation.TargetApi; 24 import android.content.BroadcastReceiver; 25 import android.content.ContentResolver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.content.res.Resources; 30 import android.database.ContentObserver; 31 import android.net.Uri; 32 import android.os.Build; 33 import android.os.Handler; 34 import android.provider.DeviceConfig; 35 import android.provider.Settings; 36 import android.telephony.SubscriptionManager; 37 import android.telephony.TelephonyCallback; 38 import android.telephony.TelephonyManager; 39 import android.util.Log; 40 41 import com.android.connectivity.resources.R; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.modules.utils.build.SdkLevel; 44 import com.android.net.module.util.DeviceConfigUtils; 45 46 import java.util.Arrays; 47 import java.util.List; 48 import java.util.concurrent.Executor; 49 import java.util.concurrent.RejectedExecutionException; 50 51 /** 52 * A class to encapsulate management of the "Smart Networking" capability of 53 * avoiding bad Wi-Fi when, for example upstream connectivity is lost or 54 * certain critical link failures occur. 55 * 56 * This enables the device to switch to another form of connectivity, like 57 * mobile, if it's available and working. 58 * 59 * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied 60 * Handler' whenever the computed "avoid bad wifi" value changes. 61 * 62 * Disabling this reverts the device to a level of networking sophistication 63 * circa 2012-13 by disabling disparate code paths each of which contribute to 64 * maintaining continuous, working Internet connectivity. 65 * 66 * @hide 67 */ 68 public class MultinetworkPolicyTracker { 69 private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); 70 71 // See Dependencies#getConfigActivelyPreferBadWifi 72 public static final String CONFIG_ACTIVELY_PREFER_BAD_WIFI = "actively_prefer_bad_wifi"; 73 74 private final Context mContext; 75 private final ConnectivityResources mResources; 76 private final Handler mHandler; 77 private final Runnable mAvoidBadWifiCallback; 78 private final List<Uri> mSettingsUris; 79 private final ContentResolver mResolver; 80 private final SettingObserver mSettingObserver; 81 private final BroadcastReceiver mBroadcastReceiver; 82 83 private volatile boolean mAvoidBadWifi = true; 84 private volatile int mMeteredMultipathPreference; 85 private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 86 private volatile long mTestAllowBadWifiUntilMs = 0; 87 88 /** 89 * Dependencies for testing 90 */ 91 @VisibleForTesting 92 public static class Dependencies { 93 /** 94 * @see DeviceConfigUtils#getDeviceConfigPropertyInt 95 */ getConfigActivelyPreferBadWifi()96 protected int getConfigActivelyPreferBadWifi() { 97 // CONFIG_ACTIVELY_PREFER_BAD_WIFI is not a feature to be rolled out, but an override 98 // for tests and an emergency kill switch (which could force the behavior on OR off). 99 // As such it uses a -1/null/1 scheme, but features should use 100 // DeviceConfigUtils#isFeatureEnabled instead, to make sure rollbacks disable the 101 // feature before it's ready on R and before. 102 return DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONNECTIVITY, 103 CONFIG_ACTIVELY_PREFER_BAD_WIFI, 0); 104 } 105 106 /** 107 @see DeviceConfig#addOnPropertiesChangedListener 108 */ addOnDevicePropertiesChangedListener(@onNull final Executor executor, @NonNull final DeviceConfig.OnPropertiesChangedListener listener)109 protected void addOnDevicePropertiesChangedListener(@NonNull final Executor executor, 110 @NonNull final DeviceConfig.OnPropertiesChangedListener listener) { 111 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONNECTIVITY, 112 executor, listener); 113 } 114 115 @VisibleForTesting 116 @NonNull getResourcesForActiveSubId( @onNull final ConnectivityResources resources, final int activeSubId)117 protected Resources getResourcesForActiveSubId( 118 @NonNull final ConnectivityResources resources, final int activeSubId) { 119 return SubscriptionManager.getResourcesForSubId( 120 resources.getResourcesContext(), activeSubId); 121 } 122 } 123 private final Dependencies mDeps; 124 125 /** 126 * Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated 127 * 128 * This setting only makes sense if the system is configured not to avoid bad wifis, i.e. 129 * if mAvoidBadWifi is true. If it's not, then no network ever yields to bad wifis 130 * ({@see FullScore#POLICY_YIELD_TO_BAD_WIFI}) and this setting has therefore no effect. 131 * 132 * If this is false, when ranking a bad wifi that never validated against cell data (or any 133 * network that yields to bad wifis), the ranker will prefer cell data. It will prefer wifi 134 * if wifi loses validation later. This behavior avoids the device losing internet access when 135 * walking past a wifi network with no internet access. 136 * This is the default behavior up to Android T, but it can be overridden through an overlay 137 * to behave like below. 138 * 139 * If this is true, then in the same scenario, the ranker will prefer cell data until 140 * the wifi completes its first validation attempt (or the attempt times out after 141 * ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS), then it will prefer the wifi even if it 142 * doesn't provide internet access, unless there is a captive portal on that wifi. 143 * This is the behavior in U and above. 144 */ 145 private boolean mActivelyPreferBadWifi; 146 147 // Mainline module can't use internal HandlerExecutor, so add an identical executor here. 148 private static class HandlerExecutor implements Executor { 149 @NonNull 150 private final Handler mHandler; 151 HandlerExecutor(@onNull Handler handler)152 HandlerExecutor(@NonNull Handler handler) { 153 mHandler = handler; 154 } 155 @Override execute(Runnable command)156 public void execute(Runnable command) { 157 if (!mHandler.post(command)) { 158 throw new RejectedExecutionException(mHandler + " is shutting down"); 159 } 160 } 161 } 162 // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed. 163 @VisibleForTesting @TargetApi(Build.VERSION_CODES.S) 164 protected class ActiveDataSubscriptionIdListener extends TelephonyCallback 165 implements TelephonyCallback.ActiveDataSubscriptionIdListener { 166 @Override onActiveDataSubscriptionIdChanged(int subId)167 public void onActiveDataSubscriptionIdChanged(int subId) { 168 mActiveSubId = subId; 169 reevaluateInternal(); 170 } 171 } 172 MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback)173 public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { 174 this(ctx, handler, avoidBadWifiCallback, new Dependencies()); 175 } 176 MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback, Dependencies deps)177 public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback, 178 Dependencies deps) { 179 mContext = ctx; 180 mResources = new ConnectivityResources(ctx); 181 mHandler = handler; 182 mAvoidBadWifiCallback = avoidBadWifiCallback; 183 mDeps = deps; 184 mSettingsUris = Arrays.asList( 185 Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), 186 Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); 187 mResolver = mContext.getContentResolver(); 188 mSettingObserver = new SettingObserver(); 189 mBroadcastReceiver = new BroadcastReceiver() { 190 @Override 191 public void onReceive(Context context, Intent intent) { 192 reevaluateInternal(); 193 } 194 }; 195 196 updateAvoidBadWifi(); 197 updateMeteredMultipathPreference(); 198 } 199 200 // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed. 201 @TargetApi(Build.VERSION_CODES.S) start()202 public void start() { 203 for (Uri uri : mSettingsUris) { 204 mResolver.registerContentObserver(uri, false, mSettingObserver); 205 } 206 207 final IntentFilter intentFilter = new IntentFilter(); 208 intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 209 mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter, 210 null /* broadcastPermission */, mHandler); 211 212 final Executor handlerExecutor = new HandlerExecutor(mHandler); 213 mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback( 214 handlerExecutor, new ActiveDataSubscriptionIdListener()); 215 mDeps.addOnDevicePropertiesChangedListener(handlerExecutor, 216 properties -> reevaluateInternal()); 217 218 reevaluate(); 219 } 220 shutdown()221 public void shutdown() { 222 mResolver.unregisterContentObserver(mSettingObserver); 223 224 mContext.unregisterReceiver(mBroadcastReceiver); 225 } 226 getAvoidBadWifi()227 public boolean getAvoidBadWifi() { 228 return mAvoidBadWifi; 229 } 230 getActivelyPreferBadWifi()231 public boolean getActivelyPreferBadWifi() { 232 return mActivelyPreferBadWifi; 233 } 234 235 // TODO: move this to MultipathPolicyTracker. getMeteredMultipathPreference()236 public int getMeteredMultipathPreference() { 237 return mMeteredMultipathPreference; 238 } 239 240 /** 241 * Whether the device or carrier configuration disables avoiding bad wifi by default. 242 */ configRestrictsAvoidBadWifi()243 public boolean configRestrictsAvoidBadWifi() { 244 final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0 245 && mTestAllowBadWifiUntilMs > System.currentTimeMillis(); 246 // If the config returns true, then avoid bad wifi design can be controlled by the 247 // NETWORK_AVOID_BAD_WIFI setting. 248 if (allowBadWifi) return true; 249 250 return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId) 251 .getInteger(R.integer.config_networkAvoidBadWifi) == 0; 252 } 253 254 /** 255 * Whether the device config prefers bad wifi actively, when it doesn't avoid them 256 * 257 * This is only relevant when the device is configured not to avoid bad wifis. In this 258 * case, "actively" preferring a bad wifi means that the device will switch to a bad 259 * wifi it just connected to, as long as it's not a captive portal. 260 * 261 * On U and above this always returns true. On T and below it reads a configuration option. 262 */ configActivelyPrefersBadWifi()263 public boolean configActivelyPrefersBadWifi() { 264 // See the definition of config_activelyPreferBadWifi for a description of its meaning. 265 // On U and above, the config is ignored, and bad wifi is always actively preferred. 266 if (SdkLevel.isAtLeastU()) return true; 267 268 // On T and below, 1 means to actively prefer bad wifi, 0 means not to prefer 269 // bad wifi (only stay stuck on it if already on there). This implementation treats 270 // any non-0 value like 1, on the assumption that anybody setting it non-zero wants 271 // the newer behavior. 272 return 0 != mDeps.getResourcesForActiveSubId(mResources, mActiveSubId) 273 .getInteger(R.integer.config_activelyPreferBadWifi); 274 } 275 276 /** 277 * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration. 278 * The value works when the time set is more than {@link System.currentTimeMillis()}. 279 */ setTestAllowBadWifiUntil(long timeMs)280 public void setTestAllowBadWifiUntil(long timeMs) { 281 Log.d(TAG, "setTestAllowBadWifiUntil: " + timeMs); 282 mTestAllowBadWifiUntilMs = timeMs; 283 reevaluateInternal(); 284 } 285 286 /** 287 * Whether we should display a notification when wifi becomes unvalidated. 288 */ shouldNotifyWifiUnvalidated()289 public boolean shouldNotifyWifiUnvalidated() { 290 return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null; 291 } 292 getAvoidBadWifiSetting()293 public String getAvoidBadWifiSetting() { 294 return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); 295 } 296 297 /** 298 * Returns whether device config says the device should actively prefer bad wifi. 299 * 300 * {@see #configActivelyPrefersBadWifi} for a description of what this does. This device 301 * config overrides that config overlay. 302 * 303 * @return True on Android U and above. 304 * True if device config says to actively prefer bad wifi. 305 * False if device config says not to actively prefer bad wifi. 306 * null if device config doesn't have an opinion (then fall back on the resource). 307 */ deviceConfigActivelyPreferBadWifi()308 public Boolean deviceConfigActivelyPreferBadWifi() { 309 if (SdkLevel.isAtLeastU()) return true; 310 switch (mDeps.getConfigActivelyPreferBadWifi()) { 311 case 1: 312 return Boolean.TRUE; 313 case -1: 314 return Boolean.FALSE; 315 default: 316 return null; 317 } 318 } 319 320 @VisibleForTesting reevaluate()321 public void reevaluate() { 322 mHandler.post(this::reevaluateInternal); 323 } 324 325 /** 326 * Reevaluate the settings. Must be called on the handler thread. 327 */ reevaluateInternal()328 private void reevaluateInternal() { 329 if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) { 330 mAvoidBadWifiCallback.run(); 331 } 332 updateMeteredMultipathPreference(); 333 } 334 updateAvoidBadWifi()335 public boolean updateAvoidBadWifi() { 336 final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting()); 337 final boolean prevAvoid = mAvoidBadWifi; 338 mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); 339 340 final boolean prevActive = mActivelyPreferBadWifi; 341 final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi(); 342 if (null == deviceConfigPreferBadWifi) { 343 mActivelyPreferBadWifi = configActivelyPrefersBadWifi(); 344 } else { 345 mActivelyPreferBadWifi = deviceConfigPreferBadWifi; 346 } 347 348 return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive; 349 } 350 351 /** 352 * The default (device and carrier-dependent) value for metered multipath preference. 353 */ configMeteredMultipathPreference()354 public int configMeteredMultipathPreference() { 355 return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId) 356 .getInteger(R.integer.config_networkMeteredMultipathPreference); 357 } 358 updateMeteredMultipathPreference()359 public void updateMeteredMultipathPreference() { 360 String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); 361 try { 362 mMeteredMultipathPreference = Integer.parseInt(setting); 363 } catch (NumberFormatException e) { 364 mMeteredMultipathPreference = configMeteredMultipathPreference(); 365 } 366 } 367 368 private class SettingObserver extends ContentObserver { SettingObserver()369 public SettingObserver() { 370 super(null); 371 } 372 373 @Override onChange(boolean selfChange)374 public void onChange(boolean selfChange) { 375 Log.wtf(TAG, "Should never be reached."); 376 } 377 378 @Override onChange(boolean selfChange, Uri uri)379 public void onChange(boolean selfChange, Uri uri) { 380 if (!mSettingsUris.contains(uri)) { 381 Log.wtf(TAG, "Unexpected settings observation: " + uri); 382 } 383 reevaluate(); 384 } 385 } 386 } 387