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