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