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 android.net.util; 18 19 import android.content.BroadcastReceiver; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.database.ContentObserver; 25 import android.net.ConnectivityManager; 26 import android.net.Uri; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.os.UserHandle; 30 import android.provider.Settings; 31 import android.util.Slog; 32 33 import java.util.Arrays; 34 import java.util.List; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.internal.R; 38 39 import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI; 40 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; 41 42 /** 43 * A class to encapsulate management of the "Smart Networking" capability of 44 * avoiding bad Wi-Fi when, for example upstream connectivity is lost or 45 * certain critical link failures occur. 46 * 47 * This enables the device to switch to another form of connectivity, like 48 * mobile, if it's available and working. 49 * 50 * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied 51 * Handler' whenever the computed "avoid bad wifi" value changes. 52 * 53 * Disabling this reverts the device to a level of networking sophistication 54 * circa 2012-13 by disabling disparate code paths each of which contribute to 55 * maintaining continuous, working Internet connectivity. 56 * 57 * @hide 58 */ 59 public class MultinetworkPolicyTracker { 60 private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); 61 62 private final Context mContext; 63 private final Handler mHandler; 64 private final Runnable mReevaluateRunnable; 65 private final List<Uri> mSettingsUris; 66 private final ContentResolver mResolver; 67 private final SettingObserver mSettingObserver; 68 private final BroadcastReceiver mBroadcastReceiver; 69 70 private volatile boolean mAvoidBadWifi = true; 71 private volatile int mMeteredMultipathPreference; 72 MultinetworkPolicyTracker(Context ctx, Handler handler)73 public MultinetworkPolicyTracker(Context ctx, Handler handler) { 74 this(ctx, handler, null); 75 } 76 MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback)77 public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { 78 mContext = ctx; 79 mHandler = handler; 80 mReevaluateRunnable = () -> { 81 if (updateAvoidBadWifi() && avoidBadWifiCallback != null) { 82 avoidBadWifiCallback.run(); 83 } 84 updateMeteredMultipathPreference(); 85 }; 86 mSettingsUris = Arrays.asList( 87 Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), 88 Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); 89 mResolver = mContext.getContentResolver(); 90 mSettingObserver = new SettingObserver(); 91 mBroadcastReceiver = new BroadcastReceiver() { 92 @Override 93 public void onReceive(Context context, Intent intent) { 94 reevaluate(); 95 } 96 }; 97 98 updateAvoidBadWifi(); 99 updateMeteredMultipathPreference(); 100 } 101 start()102 public void start() { 103 for (Uri uri : mSettingsUris) { 104 mResolver.registerContentObserver(uri, false, mSettingObserver); 105 } 106 107 final IntentFilter intentFilter = new IntentFilter(); 108 intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 109 mContext.registerReceiverAsUser( 110 mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null); 111 112 reevaluate(); 113 } 114 shutdown()115 public void shutdown() { 116 mResolver.unregisterContentObserver(mSettingObserver); 117 118 mContext.unregisterReceiver(mBroadcastReceiver); 119 } 120 getAvoidBadWifi()121 public boolean getAvoidBadWifi() { 122 return mAvoidBadWifi; 123 } 124 getMeteredMultipathPreference()125 public int getMeteredMultipathPreference() { 126 return mMeteredMultipathPreference; 127 } 128 129 /** 130 * Whether the device or carrier configuration disables avoiding bad wifi by default. 131 */ configRestrictsAvoidBadWifi()132 public boolean configRestrictsAvoidBadWifi() { 133 return (mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0); 134 } 135 136 /** 137 * Whether we should display a notification when wifi becomes unvalidated. 138 */ shouldNotifyWifiUnvalidated()139 public boolean shouldNotifyWifiUnvalidated() { 140 return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null; 141 } 142 getAvoidBadWifiSetting()143 public String getAvoidBadWifiSetting() { 144 return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); 145 } 146 147 @VisibleForTesting reevaluate()148 public void reevaluate() { 149 mHandler.post(mReevaluateRunnable); 150 } 151 updateAvoidBadWifi()152 public boolean updateAvoidBadWifi() { 153 final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting()); 154 final boolean prev = mAvoidBadWifi; 155 mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); 156 return mAvoidBadWifi != prev; 157 } 158 159 /** 160 * The default (device and carrier-dependent) value for metered multipath preference. 161 */ configMeteredMultipathPreference()162 public int configMeteredMultipathPreference() { 163 return mContext.getResources().getInteger( 164 R.integer.config_networkMeteredMultipathPreference); 165 } 166 updateMeteredMultipathPreference()167 public void updateMeteredMultipathPreference() { 168 String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); 169 try { 170 mMeteredMultipathPreference = Integer.parseInt(setting); 171 } catch (NumberFormatException e) { 172 mMeteredMultipathPreference = configMeteredMultipathPreference(); 173 } 174 } 175 176 private class SettingObserver extends ContentObserver { SettingObserver()177 public SettingObserver() { 178 super(null); 179 } 180 181 @Override onChange(boolean selfChange)182 public void onChange(boolean selfChange) { 183 Slog.wtf(TAG, "Should never be reached."); 184 } 185 186 @Override onChange(boolean selfChange, Uri uri)187 public void onChange(boolean selfChange, Uri uri) { 188 if (!mSettingsUris.contains(uri)) { 189 Slog.wtf(TAG, "Unexpected settings observation: " + uri); 190 } 191 reevaluate(); 192 } 193 } 194 } 195