1 /*
2  * Copyright (C) 2017 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 com.android.settingslib.wifi;
18 
19 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
20 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.drawable.Drawable;
25 import android.net.wifi.ScanResult;
26 import android.net.wifi.WifiConfiguration;
27 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
28 import android.net.wifi.WifiInfo;
29 import android.os.Bundle;
30 import android.os.SystemClock;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.settingslib.R;
35 
36 import java.util.Map;
37 
38 public class WifiUtils {
39 
40     private static final int INVALID_RSSI = -127;
41 
42     /**
43      * The intent action shows network details settings to allow configuration of Wi-Fi.
44      * <p>
45      * In some cases, a matching Activity may not exist, so ensure you
46      * safeguard against this.
47      * <p>
48      * Input: The calling package should put the chosen
49      * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
50      * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
51      * <p>
52      * Output: Nothing.
53      */
54     public static final String ACTION_WIFI_DETAILS_SETTINGS =
55             "android.settings.WIFI_DETAILS_SETTINGS";
56     public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
57     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
58 
59     static final int[] WIFI_PIE = {
60             com.android.internal.R.drawable.ic_wifi_signal_0,
61             com.android.internal.R.drawable.ic_wifi_signal_1,
62             com.android.internal.R.drawable.ic_wifi_signal_2,
63             com.android.internal.R.drawable.ic_wifi_signal_3,
64             com.android.internal.R.drawable.ic_wifi_signal_4
65     };
66 
67     static final int[] NO_INTERNET_WIFI_PIE = {
68             R.drawable.ic_no_internet_wifi_signal_0,
69             R.drawable.ic_no_internet_wifi_signal_1,
70             R.drawable.ic_no_internet_wifi_signal_2,
71             R.drawable.ic_no_internet_wifi_signal_3,
72             R.drawable.ic_no_internet_wifi_signal_4
73     };
74 
buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config)75     public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
76         final StringBuilder summary = new StringBuilder();
77         final WifiInfo info = accessPoint.getInfo();
78         // Add RSSI/band information for this config, what was seen up to 6 seconds ago
79         // verbose WiFi Logging is only turned on thru developers settings
80         if (accessPoint.isActive() && info != null) {
81             summary.append(" f=" + Integer.toString(info.getFrequency()));
82         }
83         summary.append(" " + getVisibilityStatus(accessPoint));
84         if (config != null
85                 && (config.getNetworkSelectionStatus().getNetworkSelectionStatus()
86                         != NETWORK_SELECTION_ENABLED)) {
87             summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
88             if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
89                 long now = System.currentTimeMillis();
90                 long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
91                 long sec = diff % 60; //seconds
92                 long min = (diff / 60) % 60; //minutes
93                 long hour = (min / 60) % 60; //hours
94                 summary.append(", ");
95                 if (hour > 0) summary.append(Long.toString(hour) + "h ");
96                 summary.append(Long.toString(min) + "m ");
97                 summary.append(Long.toString(sec) + "s ");
98             }
99             summary.append(")");
100         }
101 
102         if (config != null) {
103             NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
104             for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) {
105                 if (networkStatus.getDisableReasonCounter(reason) != 0) {
106                     summary.append(" ")
107                             .append(NetworkSelectionStatus
108                                     .getNetworkSelectionDisableReasonString(reason))
109                             .append("=")
110                             .append(networkStatus.getDisableReasonCounter(reason));
111                 }
112             }
113         }
114 
115         return summary.toString();
116     }
117 
118     /**
119      * Returns the visibility status of the WifiConfiguration.
120      *
121      * @return autojoin debugging information
122      * TODO: use a string formatter
123      * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
124      * For instance [-40,5/-30,2]
125      */
126     @VisibleForTesting
getVisibilityStatus(AccessPoint accessPoint)127     static String getVisibilityStatus(AccessPoint accessPoint) {
128         final WifiInfo info = accessPoint.getInfo();
129         StringBuilder visibility = new StringBuilder();
130         StringBuilder scans24GHz = new StringBuilder();
131         StringBuilder scans5GHz = new StringBuilder();
132         StringBuilder scans60GHz = new StringBuilder();
133         String bssid = null;
134 
135         if (accessPoint.isActive() && info != null) {
136             bssid = info.getBSSID();
137             if (bssid != null) {
138                 visibility.append(" ").append(bssid);
139             }
140             visibility.append(" standard = ").append(info.getWifiStandard());
141             visibility.append(" rssi=").append(info.getRssi());
142             visibility.append(" ");
143             visibility.append(" score=").append(info.getScore());
144             if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
145                 visibility.append(" speed=").append(accessPoint.getSpeedLabel());
146             }
147             visibility.append(String.format(" tx=%.1f,", info.getSuccessfulTxPacketsPerSecond()));
148             visibility.append(String.format("%.1f,", info.getRetriedTxPacketsPerSecond()));
149             visibility.append(String.format("%.1f ", info.getLostTxPacketsPerSecond()));
150             visibility.append(String.format("rx=%.1f", info.getSuccessfulRxPacketsPerSecond()));
151         }
152 
153         int maxRssi5 = INVALID_RSSI;
154         int maxRssi24 = INVALID_RSSI;
155         int maxRssi60 = INVALID_RSSI;
156         final int maxDisplayedScans = 4;
157         int num5 = 0; // number of scanned BSSID on 5GHz band
158         int num24 = 0; // number of scanned BSSID on 2.4Ghz band
159         int num60 = 0; // number of scanned BSSID on 60Ghz band
160         int numBlockListed = 0;
161 
162         // TODO: sort list by RSSI or age
163         long nowMs = SystemClock.elapsedRealtime();
164         for (ScanResult result : accessPoint.getScanResults()) {
165             if (result == null) {
166                 continue;
167             }
168             if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ
169                     && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) {
170                 // Strictly speaking: [4915, 5825]
171                 num5++;
172 
173                 if (result.level > maxRssi5) {
174                     maxRssi5 = result.level;
175                 }
176                 if (num5 <= maxDisplayedScans) {
177                     scans5GHz.append(
178                             verboseScanResultSummary(accessPoint, result, bssid,
179                                     nowMs));
180                 }
181             } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ
182                     && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) {
183                 // Strictly speaking: [2412, 2482]
184                 num24++;
185 
186                 if (result.level > maxRssi24) {
187                     maxRssi24 = result.level;
188                 }
189                 if (num24 <= maxDisplayedScans) {
190                     scans24GHz.append(
191                             verboseScanResultSummary(accessPoint, result, bssid,
192                                     nowMs));
193                 }
194             } else if (result.frequency >= AccessPoint.LOWER_FREQ_60GHZ
195                     && result.frequency <= AccessPoint.HIGHER_FREQ_60GHZ) {
196                 // Strictly speaking: [60000, 61000]
197                 num60++;
198 
199                 if (result.level > maxRssi60) {
200                     maxRssi60 = result.level;
201                 }
202                 if (num60 <= maxDisplayedScans) {
203                     scans60GHz.append(
204                             verboseScanResultSummary(accessPoint, result, bssid,
205                                     nowMs));
206                 }
207             }
208         }
209         visibility.append(" [");
210         if (num24 > 0) {
211             visibility.append("(").append(num24).append(")");
212             if (num24 > maxDisplayedScans) {
213                 visibility.append("max=").append(maxRssi24).append(",");
214             }
215             visibility.append(scans24GHz.toString());
216         }
217         visibility.append(";");
218         if (num5 > 0) {
219             visibility.append("(").append(num5).append(")");
220             if (num5 > maxDisplayedScans) {
221                 visibility.append("max=").append(maxRssi5).append(",");
222             }
223             visibility.append(scans5GHz.toString());
224         }
225         visibility.append(";");
226         if (num60 > 0) {
227             visibility.append("(").append(num60).append(")");
228             if (num60 > maxDisplayedScans) {
229                 visibility.append("max=").append(maxRssi60).append(",");
230             }
231             visibility.append(scans60GHz.toString());
232         }
233         if (numBlockListed > 0) {
234             visibility.append("!").append(numBlockListed);
235         }
236         visibility.append("]");
237 
238         return visibility.toString();
239     }
240 
241     @VisibleForTesting
verboseScanResultSummary(AccessPoint accessPoint, ScanResult result, String bssid, long nowMs)242     /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result,
243             String bssid, long nowMs) {
244         StringBuilder stringBuilder = new StringBuilder();
245         stringBuilder.append(" \n{").append(result.BSSID);
246         if (result.BSSID.equals(bssid)) {
247             stringBuilder.append("*");
248         }
249         stringBuilder.append("=").append(result.frequency);
250         stringBuilder.append(",").append(result.level);
251         int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache());
252         if (speed != AccessPoint.Speed.NONE) {
253             stringBuilder.append(",")
254                     .append(accessPoint.getSpeedLabel(speed));
255         }
256         int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
257         stringBuilder.append(",").append(ageSeconds).append("s");
258         stringBuilder.append("}");
259         return stringBuilder.toString();
260     }
261 
262     @AccessPoint.Speed
getSpecificApSpeed(ScanResult result, Map<String, TimestampedScoredNetwork> scoredNetworkCache)263     private static int getSpecificApSpeed(ScanResult result,
264             Map<String, TimestampedScoredNetwork> scoredNetworkCache) {
265         TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID);
266         if (timedScore == null) {
267             return AccessPoint.Speed.NONE;
268         }
269         // For debugging purposes we may want to use mRssi rather than result.level as the average
270         // speed wil be determined by mRssi
271         return timedScore.getScore().calculateBadge(result.level);
272     }
273 
getMeteredLabel(Context context, WifiConfiguration config)274     public static String getMeteredLabel(Context context, WifiConfiguration config) {
275         // meteredOverride is whether the user manually set the metered setting or not.
276         // meteredHint is whether the network itself is telling us that it is metered
277         if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED
278                 || (config.meteredHint && !isMeteredOverridden(config))) {
279             return context.getString(R.string.wifi_metered_label);
280         }
281         return context.getString(R.string.wifi_unmetered_label);
282     }
283 
284     /**
285      * Returns the Internet icon resource for a given RSSI level.
286      *
287      * @param level The number of bars to show (0-4)
288      * @param noInternet True if a connected Wi-Fi network cannot access the Internet
289      * @throws IllegalArgumentException if an invalid RSSI level is given.
290      */
getInternetIconResource(int level, boolean noInternet)291     public static int getInternetIconResource(int level, boolean noInternet) {
292         if (level < 0 || level >= WIFI_PIE.length) {
293             throw new IllegalArgumentException("No Wifi icon found for level: " + level);
294         }
295         return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
296     }
297 
298     /**
299      * Wrapper the {@link #getInternetIconResource} for testing compatibility.
300      */
301     public static class InternetIconInjector {
302 
303         protected final Context mContext;
304 
InternetIconInjector(Context context)305         public InternetIconInjector(Context context) {
306             mContext = context;
307         }
308 
309         /**
310          * Returns the Internet icon for a given RSSI level.
311          *
312          * @param noInternet True if a connected Wi-Fi network cannot access the Internet
313          * @param level The number of bars to show (0-4)
314          */
getIcon(boolean noInternet, int level)315         public Drawable getIcon(boolean noInternet, int level) {
316             return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
317         }
318     }
319 
isMeteredOverridden(WifiConfiguration config)320     public static boolean isMeteredOverridden(WifiConfiguration config) {
321         return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
322     }
323 
324     /**
325      * Returns the Intent for Wi-Fi network details settings.
326      *
327      * @param key The Wi-Fi entry key
328      */
getWifiDetailsSettingsIntent(String key)329     public static Intent getWifiDetailsSettingsIntent(String key) {
330         final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
331         final Bundle bundle = new Bundle();
332         bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
333         intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
334         return intent;
335     }
336 }
337