• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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