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