1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
5  *
6  *      http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
9  */
10 
11 package com.android.settingslib.wifi;
12 
13 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
14 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
15 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
16 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
17 
18 import android.content.Context;
19 import android.content.Intent;
20 import android.net.ConnectivityManager;
21 import android.net.ConnectivityManager.NetworkCallback;
22 import android.net.Network;
23 import android.net.NetworkCapabilities;
24 import android.net.NetworkInfo;
25 import android.net.NetworkKey;
26 import android.net.NetworkRequest;
27 import android.net.NetworkScoreManager;
28 import android.net.ScoredNetwork;
29 import android.net.wifi.WifiInfo;
30 import android.net.wifi.WifiManager;
31 import android.net.wifi.WifiNetworkScoreCache;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.provider.Settings;
35 
36 import com.android.settingslib.R;
37 import com.android.settingslib.Utils;
38 
39 import java.io.PrintWriter;
40 import java.text.SimpleDateFormat;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 
45 /**
46  * Track status of Wi-Fi for the Sys UI.
47  */
48 public class WifiStatusTracker {
49     private static final int HISTORY_SIZE = 32;
50     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
51     private final Context mContext;
52     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
53     private final WifiManager mWifiManager;
54     private final NetworkScoreManager mNetworkScoreManager;
55     private final ConnectivityManager mConnectivityManager;
56     private final Handler mHandler = new Handler(Looper.getMainLooper());
57     private final Set<Integer> mNetworks = new HashSet<>();
58     // Save the previous HISTORY_SIZE states for logging.
59     private final String[] mHistory = new String[HISTORY_SIZE];
60     // Where to copy the next state into.
61     private int mHistoryIndex;
62     private final WifiNetworkScoreCache.CacheListener mCacheListener =
63             new WifiNetworkScoreCache.CacheListener(mHandler) {
64                 @Override
65                 public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
66                     updateStatusLabel();
67                     mCallback.run();
68                 }
69             };
70     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
71             .clearCapabilities()
72             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
73             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
74             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
75     private final NetworkCallback mNetworkCallback =
76             new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
77         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
78         // and onLinkPropertiesChanged.
79         @Override
80         public void onCapabilitiesChanged(
81                 Network network, NetworkCapabilities networkCapabilities) {
82             boolean isVcnOverWifi = false;
83             boolean isWifi = false;
84             WifiInfo wifiInfo = null;
85             if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
86                 wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities);
87                 isVcnOverWifi = (wifiInfo != null);
88             } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
89                 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
90                 isWifi = true;
91             }
92             // As long as it is a WiFi network, we will log it in the dumpsys for debugging.
93             if (isVcnOverWifi || isWifi) {
94                 String log = new StringBuilder()
95                         .append(SSDF.format(System.currentTimeMillis())).append(",")
96                         .append("onCapabilitiesChanged: ")
97                         .append("network=").append(network).append(",")
98                         .append("networkCapabilities=").append(networkCapabilities)
99                         .toString();
100                 recordLastWifiNetwork(log);
101             }
102             // Ignore the WiFi network if it doesn't contain any valid WifiInfo, or it is not the
103             // primary WiFi.
104             if (wifiInfo == null || !wifiInfo.isPrimary()) {
105                 // Remove the network from the tracking list once it becomes non-primary.
106                 if (mNetworks.contains(network.getNetId())) {
107                     mNetworks.remove(network.getNetId());
108                 }
109                 return;
110             }
111             if (!mNetworks.contains(network.getNetId())) {
112                 mNetworks.add(network.getNetId());
113             }
114             updateWifiInfo(wifiInfo);
115             updateStatusLabel();
116             mCallback.run();
117         }
118 
119         @Override
120         public void onLost(Network network) {
121             String log = new StringBuilder()
122                     .append(SSDF.format(System.currentTimeMillis())).append(",")
123                     .append("onLost: ")
124                     .append("network=").append(network)
125                     .toString();
126             recordLastWifiNetwork(log);
127             if (mNetworks.contains(network.getNetId())) {
128                 mNetworks.remove(network.getNetId());
129                 updateWifiInfo(null);
130                 updateStatusLabel();
131                 mCallback.run();
132             }
133         }
134     };
135     private final NetworkCallback mDefaultNetworkCallback =
136             new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
137         @Override
138         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
139             // network is now the default network, and its capabilities are nc.
140             // This method will always be called immediately after the network becomes the
141             // default, in addition to any time the capabilities change while the network is
142             // the default.
143             mDefaultNetwork = network;
144             mDefaultNetworkCapabilities = nc;
145             updateStatusLabel();
146             mCallback.run();
147         }
148         @Override
149         public void onLost(Network network) {
150             // The system no longer has a default network.
151             mDefaultNetwork = null;
152             mDefaultNetworkCapabilities = null;
153             updateStatusLabel();
154             mCallback.run();
155         }
156     };
157     private Network mDefaultNetwork = null;
158     private NetworkCapabilities mDefaultNetworkCapabilities = null;
159     private final Runnable mCallback;
160 
161     private WifiInfo mWifiInfo;
162     public boolean enabled;
163     public boolean isCaptivePortal;
164     public boolean isDefaultNetwork;
165     public boolean isCarrierMerged;
166     public int subId;
167     public int state;
168     public boolean connected;
169     public String ssid;
170     public int rssi;
171     public int level;
172     public String statusLabel;
173 
WifiStatusTracker(Context context, WifiManager wifiManager, NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, Runnable callback)174     public WifiStatusTracker(Context context, WifiManager wifiManager,
175             NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager,
176             Runnable callback) {
177         mContext = context;
178         mWifiManager = wifiManager;
179         mWifiNetworkScoreCache = new WifiNetworkScoreCache(context);
180         mNetworkScoreManager = networkScoreManager;
181         mConnectivityManager = connectivityManager;
182         mCallback = callback;
183     }
184 
setListening(boolean listening)185     public void setListening(boolean listening) {
186         if (listening) {
187             mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
188                     mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
189             mWifiNetworkScoreCache.registerListener(mCacheListener);
190             mConnectivityManager.registerNetworkCallback(
191                     mNetworkRequest, mNetworkCallback, mHandler);
192             mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
193         } else {
194             mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI,
195                     mWifiNetworkScoreCache);
196             mWifiNetworkScoreCache.unregisterListener();
197             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
198             mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
199         }
200     }
201 
202     /**
203      * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received.
204      * This replaces the dependency on the initial sticky broadcast.
205      */
fetchInitialState()206     public void fetchInitialState() {
207         if (mWifiManager == null) {
208             return;
209         }
210         updateWifiState();
211         final NetworkInfo networkInfo =
212                 mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
213         connected = networkInfo != null && networkInfo.isConnected();
214         mWifiInfo = null;
215         ssid = null;
216         if (connected) {
217             mWifiInfo = mWifiManager.getConnectionInfo();
218             if (mWifiInfo != null) {
219                 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
220                     ssid = mWifiInfo.getPasspointProviderFriendlyName();
221                 } else {
222                     ssid = getValidSsid(mWifiInfo);
223                 }
224                 isCarrierMerged = mWifiInfo.isCarrierMerged();
225                 subId = mWifiInfo.getSubscriptionId();
226                 updateRssi(mWifiInfo.getRssi());
227                 maybeRequestNetworkScore();
228             }
229         }
230         updateStatusLabel();
231     }
232 
handleBroadcast(Intent intent)233     public void handleBroadcast(Intent intent) {
234         if (mWifiManager == null) {
235             return;
236         }
237         String action = intent.getAction();
238         if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
239             updateWifiState();
240         }
241     }
242 
updateWifiInfo(WifiInfo wifiInfo)243     private void updateWifiInfo(WifiInfo wifiInfo) {
244         updateWifiState();
245         connected = wifiInfo != null;
246         mWifiInfo = wifiInfo;
247         ssid = null;
248         if (mWifiInfo != null) {
249             if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
250                 ssid = mWifiInfo.getPasspointProviderFriendlyName();
251             } else {
252                 ssid = getValidSsid(mWifiInfo);
253             }
254             isCarrierMerged = mWifiInfo.isCarrierMerged();
255             subId = mWifiInfo.getSubscriptionId();
256             updateRssi(mWifiInfo.getRssi());
257             maybeRequestNetworkScore();
258         }
259     }
260 
updateWifiState()261     private void updateWifiState() {
262         state = mWifiManager.getWifiState();
263         enabled = state == WifiManager.WIFI_STATE_ENABLED;
264     }
265 
updateRssi(int newRssi)266     private void updateRssi(int newRssi) {
267         rssi = newRssi;
268         level = mWifiManager.calculateSignalLevel(rssi);
269     }
270 
maybeRequestNetworkScore()271     private void maybeRequestNetworkScore() {
272         NetworkKey networkKey = NetworkKey.createFromWifiInfo(mWifiInfo);
273         if (mWifiNetworkScoreCache.getScoredNetwork(networkKey) == null) {
274             mNetworkScoreManager.requestScores(new NetworkKey[]{ networkKey });
275         }
276     }
277 
updateStatusLabel()278     private void updateStatusLabel() {
279         if (mWifiManager == null) {
280             return;
281         }
282         NetworkCapabilities networkCapabilities;
283         isDefaultNetwork = false;
284         if (mDefaultNetworkCapabilities != null) {
285             boolean isWifi = mDefaultNetworkCapabilities.hasTransport(
286                     NetworkCapabilities.TRANSPORT_WIFI);
287             boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport(
288                     NetworkCapabilities.TRANSPORT_CELLULAR)
289                             && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null);
290             if (isWifi || isVcnOverWifi) {
291                 isDefaultNetwork = true;
292             }
293         }
294         if (isDefaultNetwork) {
295             // Wifi is connected and the default network.
296             networkCapabilities = mDefaultNetworkCapabilities;
297         } else {
298             networkCapabilities = mConnectivityManager.getNetworkCapabilities(
299                     mWifiManager.getCurrentNetwork());
300         }
301         isCaptivePortal = false;
302         if (networkCapabilities != null) {
303             if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
304                 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required);
305                 isCaptivePortal = true;
306                 return;
307             } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
308                 statusLabel = mContext.getString(R.string.wifi_limited_connection);
309                 return;
310             } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
311                 final String mode = Settings.Global.getString(mContext.getContentResolver(),
312                         Settings.Global.PRIVATE_DNS_MODE);
313                 if (networkCapabilities.isPrivateDnsBroken()) {
314                     statusLabel = mContext.getString(R.string.private_dns_broken);
315                 } else {
316                     statusLabel = mContext.getString(R.string.wifi_status_no_internet);
317                 }
318                 return;
319             } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
320                     && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
321                 statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
322                 return;
323             }
324         }
325 
326         ScoredNetwork scoredNetwork =
327                 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo));
328         statusLabel = scoredNetwork == null
329                 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi);
330     }
331 
332     /** Refresh the status label on Locale changed. */
refreshLocale()333     public void refreshLocale() {
334         updateStatusLabel();
335         mCallback.run();
336     }
337 
getValidSsid(WifiInfo info)338     private String getValidSsid(WifiInfo info) {
339         String ssid = info.getSSID();
340         if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) {
341             return ssid;
342         }
343         return null;
344     }
345 
recordLastWifiNetwork(String log)346     private void recordLastWifiNetwork(String log) {
347         mHistory[mHistoryIndex] = log;
348         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
349     }
350 
351     /** Dump function. */
dump(PrintWriter pw)352     public void dump(PrintWriter pw) {
353         pw.println("  - WiFi Network History ------");
354         int size = 0;
355         for (int i = 0; i < HISTORY_SIZE; i++) {
356             if (mHistory[i] != null) size++;
357         }
358         // Print out the previous states in ordered number.
359         for (int i = mHistoryIndex + HISTORY_SIZE - 1;
360                 i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
361             pw.println("  Previous WiFiNetwork("
362                     + (mHistoryIndex + HISTORY_SIZE - i) + "): "
363                     + mHistory[i & (HISTORY_SIZE - 1)]);
364         }
365     }
366 }
367