• 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 
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.HandlerThread;
34 import android.os.Looper;
35 import android.provider.Settings;
36 
37 import com.android.settingslib.R;
38 import com.android.settingslib.Utils;
39 
40 import java.io.PrintWriter;
41 import java.text.SimpleDateFormat;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Set;
45 
46 /**
47  * Track status of Wi-Fi for the Sys UI.
48  */
49 public class WifiStatusTracker {
50     private static final int HISTORY_SIZE = 32;
51     private static final SimpleDateFormat SSDF = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
52     private final Context mContext;
53     private final WifiNetworkScoreCache mWifiNetworkScoreCache;
54     private final WifiManager mWifiManager;
55     private final NetworkScoreManager mNetworkScoreManager;
56     private final ConnectivityManager mConnectivityManager;
57     private final Handler mHandler;
58     private final Handler mMainThreadHandler;
59     private final Set<Integer> mNetworks = new HashSet<>();
60     private int mPrimaryNetworkId;
61     // Save the previous HISTORY_SIZE states for logging.
62     private final String[] mHistory = new String[HISTORY_SIZE];
63     // Where to copy the next state into.
64     private int mHistoryIndex;
65     private final WifiNetworkScoreCache.CacheListener mCacheListener;
66     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
67             .clearCapabilities()
68             .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
69             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
70             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).build();
71     private final NetworkCallback mNetworkCallback =
72             new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
73         // Note: onCapabilitiesChanged is guaranteed to be called "immediately" after onAvailable
74         // and onLinkPropertiesChanged.
75         @Override
76         public void onCapabilitiesChanged(
77                 Network network, NetworkCapabilities networkCapabilities) {
78             boolean isVcnOverWifi = false;
79             boolean isWifi = false;
80             WifiInfo wifiInfo = null;
81             if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
82                 wifiInfo = Utils.tryGetWifiInfoForVcn(networkCapabilities);
83                 isVcnOverWifi = (wifiInfo != null);
84             } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
85                 wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
86                 isWifi = true;
87             }
88             // As long as it is a WiFi network, we will log it in the dumpsys for debugging.
89             if (isVcnOverWifi || 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 = false;
307         if (mDefaultNetworkCapabilities != null) {
308             boolean isWifi = mDefaultNetworkCapabilities.hasTransport(
309                     NetworkCapabilities.TRANSPORT_WIFI);
310             boolean isVcnOverWifi = mDefaultNetworkCapabilities.hasTransport(
311                     NetworkCapabilities.TRANSPORT_CELLULAR)
312                             && (Utils.tryGetWifiInfoForVcn(mDefaultNetworkCapabilities) != null);
313             if (isWifi || isVcnOverWifi) {
314                 isDefaultNetwork = true;
315             }
316         }
317         if (isDefaultNetwork) {
318             // Wifi is connected and the default network.
319             networkCapabilities = mDefaultNetworkCapabilities;
320         } else {
321             networkCapabilities = mConnectivityManager.getNetworkCapabilities(
322                     mWifiManager.getCurrentNetwork());
323         }
324         isCaptivePortal = false;
325         if (networkCapabilities != null) {
326             if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
327                 statusLabel = mContext.getString(R.string.wifi_status_sign_in_required);
328                 isCaptivePortal = true;
329                 return;
330             } else if (networkCapabilities.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
331                 statusLabel = mContext.getString(R.string.wifi_limited_connection);
332                 return;
333             } else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
334                 final String mode = Settings.Global.getString(mContext.getContentResolver(),
335                         Settings.Global.PRIVATE_DNS_MODE);
336                 if (networkCapabilities.isPrivateDnsBroken()) {
337                     statusLabel = mContext.getString(R.string.private_dns_broken);
338                 } else {
339                     statusLabel = mContext.getString(R.string.wifi_status_no_internet);
340                 }
341                 return;
342             } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
343                     && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
344                 statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
345                 return;
346             }
347         }
348 
349         ScoredNetwork scoredNetwork =
350                 mWifiNetworkScoreCache.getScoredNetwork(NetworkKey.createFromWifiInfo(mWifiInfo));
351         statusLabel = scoredNetwork == null
352                 ? null : AccessPoint.getSpeedLabel(mContext, scoredNetwork, rssi);
353     }
354 
355     /** Refresh the status label on Locale changed. */
refreshLocale()356     public void refreshLocale() {
357         updateStatusLabel();
358         mMainThreadHandler.post(() -> postResults());
359     }
360 
getValidSsid(WifiInfo info)361     private String getValidSsid(WifiInfo info) {
362         String ssid = info.getSSID();
363         if (ssid != null && !WifiManager.UNKNOWN_SSID.equals(ssid)) {
364             return ssid;
365         }
366         return null;
367     }
368 
recordLastWifiNetwork(String log)369     private void recordLastWifiNetwork(String log) {
370         mHistory[mHistoryIndex] = log;
371         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
372     }
373 
postResults()374     private void postResults() {
375         mCallback.run();
376     }
377 
378     /** Dump function. */
dump(PrintWriter pw)379     public void dump(PrintWriter pw) {
380         pw.println("  - WiFi Network History ------");
381         int size = 0;
382         for (int i = 0; i < HISTORY_SIZE; i++) {
383             if (mHistory[i] != null) size++;
384         }
385         // Print out the previous states in ordered number.
386         for (int i = mHistoryIndex + HISTORY_SIZE - 1;
387                 i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
388             pw.println("  Previous WiFiNetwork("
389                     + (mHistoryIndex + HISTORY_SIZE - i) + "): "
390                     + mHistory[i & (HISTORY_SIZE - 1)]);
391         }
392     }
393 }
394