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