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