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