• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.ConnectivityManager;
23 import android.net.ConnectivityManager.NetworkCallback;
24 import android.net.Network;
25 import android.net.NetworkCapabilities;
26 import android.net.NetworkInfo;
27 import android.net.NetworkKey;
28 import android.net.NetworkRequest;
29 import android.net.NetworkScoreManager;
30 import android.net.ScoredNetwork;
31 import android.net.TransportInfo;
32 import android.net.vcn.VcnTransportInfo;
33 import android.net.vcn.VcnUtils;
34 import android.net.wifi.WifiInfo;
35 import android.net.wifi.WifiManager;
36 import android.net.wifi.WifiNetworkScoreCache;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Looper;
40 import android.provider.Settings;
41 
42 import androidx.annotation.Nullable;
43 
44 import com.android.settingslib.R;
45 
46 import java.io.PrintWriter;
47 import java.text.SimpleDateFormat;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Set;
51 
52 /**
53  * Track status of Wi-Fi for the Sys UI.
54  */
55 @SuppressLint("MissingPermission")
56 public class WifiStatusTracker {
57     private static final int HISTORY_SIZE = 32;
58     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
59     private final Context mContext;
60     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
61     private final WifiManager mWifiManager;
62     private final NetworkScoreManager mNetworkScoreManager;
63     private final ConnectivityManager mConnectivityManager;
64     private final Handler mHandler;
65     private final Handler mMainThreadHandler;
66     private final Set<Integer> mNetworks = new HashSet<>();
67     private int mPrimaryNetworkId;
68     // Save the previous HISTORY_SIZE states for logging.
69     private final String[] mHistory = new String[HISTORY_SIZE];
70     // Where to copy the next state into.
71     private int mHistoryIndex;
72     private final WifiNetworkScoreCache.CacheListener mCacheListener;
73     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
74             .clearCapabilities()
75             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
76             .addTransportType(TRANSPORT_WIFI)
77             .addTransportType(TRANSPORT_CELLULAR)
78             .build();
79     private final NetworkCallback mNetworkCallback =
80             new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
81         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
82         // and onLinkPropertiesChanged.
83         @Override
84         public void onCapabilitiesChanged(
85                 Network network, NetworkCapabilities networkCapabilities) {
86             WifiInfo wifiInfo = getMainOrUnderlyingWifiInfo(networkCapabilities);
87             boolean isWifi = connectionIsWifi(networkCapabilities, wifiInfo);
88             // As long as it is a WiFi network, we will log it in the dumpsys for debugging.
89             if (isWifi) {
90                 String log = new StringBuilder()
91                         .append(SSDF.format(System.currentTimeMillis())).append(",")
92                         .append("onCapabilitiesChanged: ")
93                         .append("network=").append(network).append(",")
94                         .append("networkCapabilities=").append(networkCapabilities)
95                         .toString();
96                 recordLastWifiNetwork(log);
97             }
98             // Ignore the WiFi network if it doesn't contain any valid WifiInfo, or it is not the
99             // primary WiFi.
100             if (wifiInfo == null || !wifiInfo.isPrimary()) {
101                 // Remove the network from the tracking list once it becomes non-primary.
102                 if (mNetworks.contains(network.getNetId())) {
103                     mNetworks.remove(network.getNetId());
104                 }
105                 return;
106             }
107             if (!mNetworks.contains(network.getNetId())) {
108                 mNetworks.add(network.getNetId());
109             }
110             mPrimaryNetworkId = network.getNetId();
111             updateWifiInfo(wifiInfo);
112             updateStatusLabel();
113             mMainThreadHandler.post(() -> postResults());
114         }
115 
116         @Override
117         public void onLost(Network network) {
118             String log = new StringBuilder()
119                     .append(SSDF.format(System.currentTimeMillis())).append(",")
120                     .append("onLost: ")
121                     .append("network=").append(network)
122                     .toString();
123             recordLastWifiNetwork(log);
124             if (mNetworks.contains(network.getNetId())) {
125                 mNetworks.remove(network.getNetId());
126             }
127             if (network.getNetId() != mPrimaryNetworkId) {
128                 return;
129             }
130             updateWifiInfo(null);
131             updateStatusLabel();
132             mMainThreadHandler.post(() -> postResults());
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             mMainThreadHandler.post(() -> postResults());
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             mMainThreadHandler.post(() -> postResults());
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         this(context, wifiManager, networkScoreManager, connectivityManager, callback, null, null);
178     }
179 
WifiStatusTracker(Context context, WifiManager wifiManager, NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager, Runnable callback, Handler foregroundHandler, Handler backgroundHandler)180     public WifiStatusTracker(Context context, WifiManager wifiManager,
181             NetworkScoreManager networkScoreManager, ConnectivityManager connectivityManager,
182             Runnable callback, Handler foregroundHandler, Handler backgroundHandler) {
183         mContext = context;
184         mWifiManager = wifiManager;
185         mWifiNetworkScoreCache = new WifiNetworkScoreCache(context);
186         mNetworkScoreManager = networkScoreManager;
187         mConnectivityManager = connectivityManager;
188         mCallback = callback;
189         if (backgroundHandler == null) {
190             HandlerThread handlerThread = new HandlerThread("WifiStatusTrackerHandler");
191             handlerThread.start();
192             mHandler = new Handler(handlerThread.getLooper());
193         } else {
194             mHandler = backgroundHandler;
195         }
196         mMainThreadHandler = foregroundHandler == null
197                 ? new Handler(Looper.getMainLooper()) : foregroundHandler;
198         mCacheListener =
199                 new WifiNetworkScoreCache.CacheListener(mHandler) {
200                     @Override
201                     public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
202                         updateStatusLabel();
203                         mMainThreadHandler.post(() -> postResults());
204                     }
205                 };
206     }
207 
setListening(boolean listening)208     public void setListening(boolean listening) {
209         if (listening) {
210             mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
211                     mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
212             mWifiNetworkScoreCache.registerListener(mCacheListener);
213             mConnectivityManager.registerNetworkCallback(
214                     mNetworkRequest, mNetworkCallback, mHandler);
215             mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, mHandler);
216         } else {
217             mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI,
218                     mWifiNetworkScoreCache);
219             mWifiNetworkScoreCache.unregisterListener();
220             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
221             mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
222         }
223     }
224 
225     /**
226      * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received.
227      * This replaces the dependency on the initial sticky broadcast.
228      */
fetchInitialState()229     public void fetchInitialState() {
230         if (mWifiManager == null) {
231             return;
232         }
233         updateWifiState();
234         final NetworkInfo networkInfo =
235                 mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
236         connected = networkInfo != null && networkInfo.isConnected();
237         mWifiInfo = null;
238         ssid = null;
239         if (connected) {
240             mWifiInfo = mWifiManager.getConnectionInfo();
241             if (mWifiInfo != null) {
242                 if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
243                     ssid = mWifiInfo.getPasspointProviderFriendlyName();
244                 } else {
245                     ssid = getValidSsid(mWifiInfo);
246                 }
247                 isCarrierMerged = mWifiInfo.isCarrierMerged();
248                 subId = mWifiInfo.getSubscriptionId();
249                 updateRssi(mWifiInfo.getRssi());
250                 maybeRequestNetworkScore();
251             }
252         }
253         updateStatusLabel();
254     }
255 
handleBroadcast(Intent intent)256     public void handleBroadcast(Intent intent) {
257         if (mWifiManager == null) {
258             return;
259         }
260         String action = intent.getAction();
261         if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
262             updateWifiState();
263         }
264     }
265 
updateWifiInfo(WifiInfo wifiInfo)266     private void updateWifiInfo(WifiInfo wifiInfo) {
267         updateWifiState();
268         connected = wifiInfo != null;
269         mWifiInfo = wifiInfo;
270         ssid = null;
271         if (mWifiInfo != null) {
272             if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
273                 ssid = mWifiInfo.getPasspointProviderFriendlyName();
274             } else {
275                 ssid = getValidSsid(mWifiInfo);
276             }
277             isCarrierMerged = mWifiInfo.isCarrierMerged();
278             subId = mWifiInfo.getSubscriptionId();
279             updateRssi(mWifiInfo.getRssi());
280             maybeRequestNetworkScore();
281         }
282     }
283 
updateWifiState()284     private void updateWifiState() {
285         state = mWifiManager.getWifiState();
286         enabled = state == WifiManager.WIFI_STATE_ENABLED;
287     }
288 
updateRssi(int newRssi)289     private void updateRssi(int newRssi) {
290         rssi = newRssi;
291         level = mWifiManager.calculateSignalLevel(rssi);
292     }
293 
maybeRequestNetworkScore()294     private void maybeRequestNetworkScore() {
295         NetworkKey networkKey = NetworkKey.createFromWifiInfo(mWifiInfo);
296         if (mWifiNetworkScoreCache.getScoredNetwork(networkKey) == null) {
297             mNetworkScoreManager.requestScores(new NetworkKey[]{ networkKey });
298         }
299     }
300 
updateStatusLabel()301     private void updateStatusLabel() {
302         if (mWifiManager == null) {
303             return;
304         }
305         NetworkCapabilities networkCapabilities;
306         isDefaultNetwork = mDefaultNetworkCapabilities != null
307                 && connectionIsWifi(mDefaultNetworkCapabilities);
308         if (isDefaultNetwork) {
309             // Wifi is connected and the default network.
310             networkCapabilities = mDefaultNetworkCapabilities;
311         } else {
312             networkCapabilities = mConnectivityManager.getNetworkCapabilities(
313                     mWifiManager.getCurrentNetwork());
314         }
315         isCaptivePortal = false;
316         if (networkCapabilities != null) {
317             if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
318                 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required);
319                 isCaptivePortal = true;
320                 return;
321             } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
322                 statusLabel = mContext.getString(R.string.wifi_limited_connection);
323                 return;
324             } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
325                 final String mode = Settings.Global.getString(mContext.getContentResolver(),
326                         Settings.Global.PRIVATE_DNS_MODE);
327                 if (networkCapabilities.isPrivateDnsBroken()) {
328                     statusLabel = mContext.getString(R.string.private_dns_broken);
329                 } else {
330                     statusLabel = mContext.getString(R.string.wifi_status_no_internet);
331                 }
332                 return;
333             } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
334                     && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
335                 statusLabel = mContext.getString(
336                         com.android.wifitrackerlib.R.string.wifi_connected_low_quality);
337                 return;
338             }
339         }
340 
341         ScoredNetwork scoredNetwork =
342                 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo));
343         statusLabel = scoredNetwork == null
344                 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi);
345     }
346 
347     @Nullable
getMainOrUnderlyingWifiInfo( @ullable NetworkCapabilities networkCapabilities)348     private WifiInfo getMainOrUnderlyingWifiInfo(
349             @Nullable NetworkCapabilities networkCapabilities) {
350         if (networkCapabilities == null) {
351             return null;
352         }
353 
354         WifiInfo mainWifiInfo = getMainWifiInfo(networkCapabilities);
355         if (mainWifiInfo != null) {
356             return mainWifiInfo;
357         }
358 
359         // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
360         // so skip the underlying network check if it's not CELLULAR.
361         if (!networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
362             return mainWifiInfo;
363         }
364 
365         List<Network> underlyingNetworks = networkCapabilities.getUnderlyingNetworks();
366         if (underlyingNetworks == null) {
367             return null;
368         }
369 
370         // Some connections, like VPN connections, may have underlying networks that are
371         // eventually traced to a wifi or carrier merged connection. So, check those underlying
372         // networks for possible wifi information as well. See b/225902574.
373         for (Network underlyingNetwork : underlyingNetworks) {
374             NetworkCapabilities underlyingNetworkCapabilities =
375                     mConnectivityManager.getNetworkCapabilities(underlyingNetwork);
376             WifiInfo underlyingWifiInfo = getMainWifiInfo(underlyingNetworkCapabilities);
377             if (underlyingWifiInfo != null) {
378                 return underlyingWifiInfo;
379             }
380         }
381 
382         return null;
383     }
384 
385     @Nullable
getMainWifiInfo(@ullable NetworkCapabilities networkCapabilities)386     private WifiInfo getMainWifiInfo(@Nullable NetworkCapabilities networkCapabilities) {
387         if (networkCapabilities == null) {
388             return null;
389         }
390         boolean canHaveWifiInfo = networkCapabilities.hasTransport(TRANSPORT_WIFI)
391                 || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
392         if (!canHaveWifiInfo) {
393             return null;
394         }
395 
396         TransportInfo transportInfo = networkCapabilities.getTransportInfo();
397         if (transportInfo instanceof VcnTransportInfo) {
398             return VcnUtils.getWifiInfoFromVcnCaps(mConnectivityManager, networkCapabilities);
399         } else if (transportInfo instanceof WifiInfo) {
400             return (WifiInfo) transportInfo;
401         } else {
402             return null;
403         }
404     }
405 
connectionIsWifi(NetworkCapabilities networkCapabilities)406     private boolean connectionIsWifi(NetworkCapabilities networkCapabilities) {
407         return connectionIsWifi(
408                 networkCapabilities,
409                 getMainOrUnderlyingWifiInfo(networkCapabilities));
410     }
411 
connectionIsWifi( @ullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo)412     private boolean connectionIsWifi(
413             @Nullable NetworkCapabilities networkCapabilities, WifiInfo wifiInfo) {
414         if (networkCapabilities == null) {
415             return false;
416         }
417         return wifiInfo != null || networkCapabilities.hasTransport(TRANSPORT_WIFI);
418     }
419 
420     /** Refresh the status label on Locale changed. */
refreshLocale()421     public void refreshLocale() {
422         updateStatusLabel();
423         mMainThreadHandler.post(() -> postResults());
424     }
425 
getValidSsid(WifiInfo info)426     private String getValidSsid(WifiInfo info) {
427         String ssid = info.getSSID();
428         if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) {
429             return ssid;
430         }
431         return null;
432     }
433 
recordLastWifiNetwork(String log)434     private void recordLastWifiNetwork(String log) {
435         mHistory[mHistoryIndex] = log;
436         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
437     }
438 
postResults()439     private void postResults() {
440         mCallback.run();
441     }
442 
443     /** Dump function. */
dump(PrintWriter pw)444     public void dump(PrintWriter pw) {
445         pw.println("  - WiFi Network History ------");
446         int size = 0;
447         for (int i = 0; i < HISTORY_SIZE; i++) {
448             if (mHistory[i] != null) size++;
449         }
450         // Print out the previous states in ordered number.
451         for (int i = mHistoryIndex + HISTORY_SIZE - 1;
452                 i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
453             pw.println("  Previous WiFiNetwork("
454                     + (mHistoryIndex + HISTORY_SIZE - i) + "): "
455                     + mHistory[i & (HISTORY_SIZE - 1)]);
456         }
457     }
458 }
459