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.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.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.ConnectivityResources; 31 import android.net.Uri; 32 import android.os.Handler; 33 import android.provider.Settings; 34 import android.telephony.SubscriptionManager; 35 import android.telephony.TelephonyCallback; 36 import android.telephony.TelephonyManager; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 41 import java.util.Arrays; 42 import java.util.List; 43 import java.util.concurrent.Executor; 44 import java.util.concurrent.RejectedExecutionException; 45 46 /** 47 * A class to encapsulate management of the "Smart Networking" capability of 48 * avoiding bad Wi-Fi when, for example upstream connectivity is lost or 49 * certain critical link failures occur. 50 * 51 * This enables the device to switch to another form of connectivity, like 52 * mobile, if it's available and working. 53 * 54 * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied 55 * Handler' whenever the computed "avoid bad wifi" value changes. 56 * 57 * Disabling this reverts the device to a level of networking sophistication 58 * circa 2012-13 by disabling disparate code paths each of which contribute to 59 * maintaining continuous, working Internet connectivity. 60 * 61 * @hide 62 */ 63 public class MultinetworkPolicyTracker { 64 private static String TAG = MultinetworkPolicyTracker.class.getSimpleName(); 65 66 private final Context mContext; 67 private final ConnectivityResources mResources; 68 private final Handler mHandler; 69 private final Runnable mAvoidBadWifiCallback; 70 private final List<Uri> mSettingsUris; 71 private final ContentResolver mResolver; 72 private final SettingObserver mSettingObserver; 73 private final BroadcastReceiver mBroadcastReceiver; 74 75 private volatile boolean mAvoidBadWifi = true; 76 private volatile int mMeteredMultipathPreference; 77 private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 78 private volatile long mTestAllowBadWifiUntilMs = 0; 79 80 // Mainline module can't use internal HandlerExecutor, so add an identical executor here. 81 private static class HandlerExecutor implements Executor { 82 @NonNull 83 private final Handler mHandler; 84 HandlerExecutor(@onNull Handler handler)85 HandlerExecutor(@NonNull Handler handler) { 86 mHandler = handler; 87 } 88 @Override execute(Runnable command)89 public void execute(Runnable command) { 90 if (!mHandler.post(command)) { 91 throw new RejectedExecutionException(mHandler + " is shutting down"); 92 } 93 } 94 } 95 96 @VisibleForTesting 97 protected class ActiveDataSubscriptionIdListener extends TelephonyCallback 98 implements TelephonyCallback.ActiveDataSubscriptionIdListener { 99 @Override onActiveDataSubscriptionIdChanged(int subId)100 public void onActiveDataSubscriptionIdChanged(int subId) { 101 mActiveSubId = subId; 102 reevaluateInternal(); 103 } 104 } 105 MultinetworkPolicyTracker(Context ctx, Handler handler)106 public MultinetworkPolicyTracker(Context ctx, Handler handler) { 107 this(ctx, handler, null); 108 } 109 MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback)110 public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) { 111 mContext = ctx; 112 mResources = new ConnectivityResources(ctx); 113 mHandler = handler; 114 mAvoidBadWifiCallback = avoidBadWifiCallback; 115 mSettingsUris = Arrays.asList( 116 Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI), 117 Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE)); 118 mResolver = mContext.getContentResolver(); 119 mSettingObserver = new SettingObserver(); 120 mBroadcastReceiver = new BroadcastReceiver() { 121 @Override 122 public void onReceive(Context context, Intent intent) { 123 reevaluateInternal(); 124 } 125 }; 126 127 ctx.getSystemService(TelephonyManager.class).registerTelephonyCallback( 128 new HandlerExecutor(handler), new ActiveDataSubscriptionIdListener()); 129 130 updateAvoidBadWifi(); 131 updateMeteredMultipathPreference(); 132 } 133 start()134 public void start() { 135 for (Uri uri : mSettingsUris) { 136 mResolver.registerContentObserver(uri, false, mSettingObserver); 137 } 138 139 final IntentFilter intentFilter = new IntentFilter(); 140 intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 141 mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter, 142 null /* broadcastPermission */, mHandler); 143 144 reevaluate(); 145 } 146 shutdown()147 public void shutdown() { 148 mResolver.unregisterContentObserver(mSettingObserver); 149 150 mContext.unregisterReceiver(mBroadcastReceiver); 151 } 152 getAvoidBadWifi()153 public boolean getAvoidBadWifi() { 154 return mAvoidBadWifi; 155 } 156 157 // TODO: move this to MultipathPolicyTracker. getMeteredMultipathPreference()158 public int getMeteredMultipathPreference() { 159 return mMeteredMultipathPreference; 160 } 161 162 /** 163 * Whether the device or carrier configuration disables avoiding bad wifi by default. 164 */ configRestrictsAvoidBadWifi()165 public boolean configRestrictsAvoidBadWifi() { 166 final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0 167 && mTestAllowBadWifiUntilMs > System.currentTimeMillis(); 168 // If the config returns true, then avoid bad wifi design can be controlled by the 169 // NETWORK_AVOID_BAD_WIFI setting. 170 if (allowBadWifi) return true; 171 172 // TODO: use R.integer.config_networkAvoidBadWifi directly 173 final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi", 174 "integer", mResources.getResourcesContext().getPackageName()); 175 return (getResourcesForActiveSubId().getInteger(id) == 0); 176 } 177 178 /** 179 * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration. 180 * The value works when the time set is more than {@link System.currentTimeMillis()}. 181 */ setTestAllowBadWifiUntil(long timeMs)182 public void setTestAllowBadWifiUntil(long timeMs) { 183 Log.d(TAG, "setTestAllowBadWifiUntil: " + mTestAllowBadWifiUntilMs); 184 mTestAllowBadWifiUntilMs = timeMs; 185 reevaluateInternal(); 186 } 187 188 @VisibleForTesting 189 @NonNull getResourcesForActiveSubId()190 protected Resources getResourcesForActiveSubId() { 191 return SubscriptionManager.getResourcesForSubId( 192 mResources.getResourcesContext(), mActiveSubId); 193 } 194 195 /** 196 * Whether we should display a notification when wifi becomes unvalidated. 197 */ shouldNotifyWifiUnvalidated()198 public boolean shouldNotifyWifiUnvalidated() { 199 return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null; 200 } 201 getAvoidBadWifiSetting()202 public String getAvoidBadWifiSetting() { 203 return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); 204 } 205 206 @VisibleForTesting reevaluate()207 public void reevaluate() { 208 mHandler.post(this::reevaluateInternal); 209 } 210 211 /** 212 * Reevaluate the settings. Must be called on the handler thread. 213 */ reevaluateInternal()214 private void reevaluateInternal() { 215 if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) { 216 mAvoidBadWifiCallback.run(); 217 } 218 updateMeteredMultipathPreference(); 219 } 220 updateAvoidBadWifi()221 public boolean updateAvoidBadWifi() { 222 final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting()); 223 final boolean prev = mAvoidBadWifi; 224 mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi(); 225 return mAvoidBadWifi != prev; 226 } 227 228 /** 229 * The default (device and carrier-dependent) value for metered multipath preference. 230 */ configMeteredMultipathPreference()231 public int configMeteredMultipathPreference() { 232 // TODO: use R.integer.config_networkMeteredMultipathPreference directly 233 final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference", 234 "integer", mResources.getResourcesContext().getPackageName()); 235 return mResources.get().getInteger(id); 236 } 237 updateMeteredMultipathPreference()238 public void updateMeteredMultipathPreference() { 239 String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE); 240 try { 241 mMeteredMultipathPreference = Integer.parseInt(setting); 242 } catch (NumberFormatException e) { 243 mMeteredMultipathPreference = configMeteredMultipathPreference(); 244 } 245 } 246 247 private class SettingObserver extends ContentObserver { SettingObserver()248 public SettingObserver() { 249 super(null); 250 } 251 252 @Override onChange(boolean selfChange)253 public void onChange(boolean selfChange) { 254 Log.wtf(TAG, "Should never be reached."); 255 } 256 257 @Override onChange(boolean selfChange, Uri uri)258 public void onChange(boolean selfChange, Uri uri) { 259 if (!mSettingsUris.contains(uri)) { 260 Log.wtf(TAG, "Unexpected settings observation: " + uri); 261 } 262 reevaluate(); 263 } 264 } 265 } 266