Home | History | Annotate | Download | only in util
      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 
     73     public MultinetworkPolicyTracker(Context ctx, Handler handler) {
     74         this(ctx, handler, null);
     75     }
     76 
     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 
    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 
    115     public void shutdown() {
    116         mResolver.unregisterContentObserver(mSettingObserver);
    117 
    118         mContext.unregisterReceiver(mBroadcastReceiver);
    119     }
    120 
    121     public boolean getAvoidBadWifi() {
    122         return mAvoidBadWifi;
    123     }
    124 
    125     // TODO: move this to MultipathPolicyTracker.
    126     public int getMeteredMultipathPreference() {
    127         return mMeteredMultipathPreference;
    128     }
    129 
    130     /**
    131      * Whether the device or carrier configuration disables avoiding bad wifi by default.
    132      */
    133     public boolean configRestrictsAvoidBadWifi() {
    134         return (mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0);
    135     }
    136 
    137     /**
    138      * Whether we should display a notification when wifi becomes unvalidated.
    139      */
    140     public boolean shouldNotifyWifiUnvalidated() {
    141         return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
    142     }
    143 
    144     public String getAvoidBadWifiSetting() {
    145         return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
    146     }
    147 
    148     @VisibleForTesting
    149     public void reevaluate() {
    150         mHandler.post(mReevaluateRunnable);
    151     }
    152 
    153     public boolean updateAvoidBadWifi() {
    154         final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
    155         final boolean prev = mAvoidBadWifi;
    156         mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
    157         return mAvoidBadWifi != prev;
    158     }
    159 
    160     /**
    161      * The default (device and carrier-dependent) value for metered multipath preference.
    162      */
    163     public int configMeteredMultipathPreference() {
    164         return mContext.getResources().getInteger(
    165                 R.integer.config_networkMeteredMultipathPreference);
    166     }
    167 
    168     public void updateMeteredMultipathPreference() {
    169         String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
    170         try {
    171             mMeteredMultipathPreference = Integer.parseInt(setting);
    172         } catch (NumberFormatException e) {
    173             mMeteredMultipathPreference = configMeteredMultipathPreference();
    174         }
    175     }
    176 
    177     private class SettingObserver extends ContentObserver {
    178         public SettingObserver() {
    179             super(null);
    180         }
    181 
    182         @Override
    183         public void onChange(boolean selfChange) {
    184             Slog.wtf(TAG, "Should never be reached.");
    185         }
    186 
    187         @Override
    188         public void onChange(boolean selfChange, Uri uri) {
    189             if (!mSettingsUris.contains(uri)) {
    190                 Slog.wtf(TAG, "Unexpected settings observation: " + uri);
    191             }
    192             reevaluate();
    193         }
    194     }
    195 }
    196