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
17package android.net.util;
18
19import android.content.BroadcastReceiver;
20import android.content.ContentResolver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.database.ContentObserver;
25import android.net.ConnectivityManager;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.Message;
29import android.os.UserHandle;
30import android.provider.Settings;
31import android.util.Slog;
32
33import java.util.Arrays;
34import java.util.List;
35
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.internal.R;
38
39import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
40import 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 */
59public 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    public int getMeteredMultipathPreference() {
126        return mMeteredMultipathPreference;
127    }
128
129    /**
130     * Whether the device or carrier configuration disables avoiding bad wifi by default.
131     */
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     */
139    public boolean shouldNotifyWifiUnvalidated() {
140        return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
141    }
142
143    public String getAvoidBadWifiSetting() {
144        return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
145    }
146
147    @VisibleForTesting
148    public void reevaluate() {
149        mHandler.post(mReevaluateRunnable);
150    }
151
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     */
162    public int configMeteredMultipathPreference() {
163        return mContext.getResources().getInteger(
164                R.integer.config_networkMeteredMultipathPreference);
165    }
166
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 {
177        public SettingObserver() {
178            super(null);
179        }
180
181        @Override
182        public void onChange(boolean selfChange) {
183            Slog.wtf(TAG, "Should never be reached.");
184        }
185
186        @Override
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