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.net.wifi.ScanResult;
24 import android.net.wifi.WifiConfiguration;
25 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
26 import android.net.wifi.WifiInfo;
27 import android.os.SystemClock;
28 
29 import androidx.annotation.VisibleForTesting;
30 
31 import com.android.settingslib.R;
32 
33 import java.util.Map;
34 
35 public class WifiUtils {
36 
37     private static final int INVALID_RSSI = -127;
38 
buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config)39     public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
40         final StringBuilder summary = new StringBuilder();
41         final WifiInfo info = accessPoint.getInfo();
42         // Add RSSI/band information for this config, what was seen up to 6 seconds ago
43         // verbose WiFi Logging is only turned on thru developers settings
44         if (accessPoint.isActive() && info != null) {
45             summary.append(" f=" + Integer.toString(info.getFrequency()));
46         }
47         summary.append(" " + getVisibilityStatus(accessPoint));
48         if (config != null
49                 && (config.getNetworkSelectionStatus().getNetworkSelectionStatus()
50                         != NETWORK_SELECTION_ENABLED)) {
51             summary.append(" (" + config.getNetworkSelectionStatus().getNetworkStatusString());
52             if (config.getNetworkSelectionStatus().getDisableTime() > 0) {
53                 long now = System.currentTimeMillis();
54                 long diff = (now - config.getNetworkSelectionStatus().getDisableTime()) / 1000;
55                 long sec = diff % 60; //seconds
56                 long min = (diff / 60) % 60; //minutes
57                 long hour = (min / 60) % 60; //hours
58                 summary.append(", ");
59                 if (hour > 0) summary.append(Long.toString(hour) + "h ");
60                 summary.append(Long.toString(min) + "m ");
61                 summary.append(Long.toString(sec) + "s ");
62             }
63             summary.append(")");
64         }
65 
66         if (config != null) {
67             NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
68             for (int reason = 0; reason <= getMaxNetworkSelectionDisableReason(); reason++) {
69                 if (networkStatus.getDisableReasonCounter(reason) != 0) {
70                     summary.append(" ")
71                             .append(NetworkSelectionStatus
72                                     .getNetworkSelectionDisableReasonString(reason))
73                             .append("=")
74                             .append(networkStatus.getDisableReasonCounter(reason));
75                 }
76             }
77         }
78 
79         return summary.toString();
80     }
81 
82     /**
83      * Returns the visibility status of the WifiConfiguration.
84      *
85      * @return autojoin debugging information
86      * TODO: use a string formatter
87      * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"]
88      * For instance [-40,5/-30,2]
89      */
90     @VisibleForTesting
getVisibilityStatus(AccessPoint accessPoint)91     static String getVisibilityStatus(AccessPoint accessPoint) {
92         final WifiInfo info = accessPoint.getInfo();
93         StringBuilder visibility = new StringBuilder();
94         StringBuilder scans24GHz = new StringBuilder();
95         StringBuilder scans5GHz = new StringBuilder();
96         String bssid = null;
97 
98         if (accessPoint.isActive() && info != null) {
99             bssid = info.getBSSID();
100             if (bssid != null) {
101                 visibility.append(" ").append(bssid);
102             }
103             visibility.append(" standard = ").append(info.getWifiStandard());
104             visibility.append(" rssi=").append(info.getRssi());
105             visibility.append(" ");
106             visibility.append(" score=").append(info.getScore());
107             if (accessPoint.getSpeed() != AccessPoint.Speed.NONE) {
108                 visibility.append(" speed=").append(accessPoint.getSpeedLabel());
109             }
110             visibility.append(String.format(" tx=%.1f,", info.getSuccessfulTxPacketsPerSecond()));
111             visibility.append(String.format("%.1f,", info.getRetriedTxPacketsPerSecond()));
112             visibility.append(String.format("%.1f ", info.getLostTxPacketsPerSecond()));
113             visibility.append(String.format("rx=%.1f", info.getSuccessfulRxPacketsPerSecond()));
114         }
115 
116         int maxRssi5 = INVALID_RSSI;
117         int maxRssi24 = INVALID_RSSI;
118         final int maxDisplayedScans = 4;
119         int num5 = 0; // number of scanned BSSID on 5GHz band
120         int num24 = 0; // number of scanned BSSID on 2.4Ghz band
121         int numBlackListed = 0;
122 
123         // TODO: sort list by RSSI or age
124         long nowMs = SystemClock.elapsedRealtime();
125         for (ScanResult result : accessPoint.getScanResults()) {
126             if (result == null) {
127                 continue;
128             }
129             if (result.frequency >= AccessPoint.LOWER_FREQ_5GHZ
130                     && result.frequency <= AccessPoint.HIGHER_FREQ_5GHZ) {
131                 // Strictly speaking: [4915, 5825]
132                 num5++;
133 
134                 if (result.level > maxRssi5) {
135                     maxRssi5 = result.level;
136                 }
137                 if (num5 <= maxDisplayedScans) {
138                     scans5GHz.append(
139                             verboseScanResultSummary(accessPoint, result, bssid,
140                                     nowMs));
141                 }
142             } else if (result.frequency >= AccessPoint.LOWER_FREQ_24GHZ
143                     && result.frequency <= AccessPoint.HIGHER_FREQ_24GHZ) {
144                 // Strictly speaking: [2412, 2482]
145                 num24++;
146 
147                 if (result.level > maxRssi24) {
148                     maxRssi24 = result.level;
149                 }
150                 if (num24 <= maxDisplayedScans) {
151                     scans24GHz.append(
152                             verboseScanResultSummary(accessPoint, result, bssid,
153                                     nowMs));
154                 }
155             }
156         }
157         visibility.append(" [");
158         if (num24 > 0) {
159             visibility.append("(").append(num24).append(")");
160             if (num24 > maxDisplayedScans) {
161                 visibility.append("max=").append(maxRssi24).append(",");
162             }
163             visibility.append(scans24GHz.toString());
164         }
165         visibility.append(";");
166         if (num5 > 0) {
167             visibility.append("(").append(num5).append(")");
168             if (num5 > maxDisplayedScans) {
169                 visibility.append("max=").append(maxRssi5).append(",");
170             }
171             visibility.append(scans5GHz.toString());
172         }
173         if (numBlackListed > 0) {
174             visibility.append("!").append(numBlackListed);
175         }
176         visibility.append("]");
177 
178         return visibility.toString();
179     }
180 
181     @VisibleForTesting
verboseScanResultSummary(AccessPoint accessPoint, ScanResult result, String bssid, long nowMs)182     /* package */ static String verboseScanResultSummary(AccessPoint accessPoint, ScanResult result,
183             String bssid, long nowMs) {
184         StringBuilder stringBuilder = new StringBuilder();
185         stringBuilder.append(" \n{").append(result.BSSID);
186         if (result.BSSID.equals(bssid)) {
187             stringBuilder.append("*");
188         }
189         stringBuilder.append("=").append(result.frequency);
190         stringBuilder.append(",").append(result.level);
191         int speed = getSpecificApSpeed(result, accessPoint.getScoredNetworkCache());
192         if (speed != AccessPoint.Speed.NONE) {
193             stringBuilder.append(",")
194                     .append(accessPoint.getSpeedLabel(speed));
195         }
196         int ageSeconds = (int) (nowMs - result.timestamp / 1000) / 1000;
197         stringBuilder.append(",").append(ageSeconds).append("s");
198         stringBuilder.append("}");
199         return stringBuilder.toString();
200     }
201 
202     @AccessPoint.Speed
getSpecificApSpeed(ScanResult result, Map<String, TimestampedScoredNetwork> scoredNetworkCache)203     private static int getSpecificApSpeed(ScanResult result,
204             Map<String, TimestampedScoredNetwork> scoredNetworkCache) {
205         TimestampedScoredNetwork timedScore = scoredNetworkCache.get(result.BSSID);
206         if (timedScore == null) {
207             return AccessPoint.Speed.NONE;
208         }
209         // For debugging purposes we may want to use mRssi rather than result.level as the average
210         // speed wil be determined by mRssi
211         return timedScore.getScore().calculateBadge(result.level);
212     }
213 
getMeteredLabel(Context context, WifiConfiguration config)214     public static String getMeteredLabel(Context context, WifiConfiguration config) {
215         // meteredOverride is whether the user manually set the metered setting or not.
216         // meteredHint is whether the network itself is telling us that it is metered
217         if (config.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED
218                 || (config.meteredHint && !isMeteredOverridden(config))) {
219             return context.getString(R.string.wifi_metered_label);
220         }
221         return context.getString(R.string.wifi_unmetered_label);
222     }
223 
isMeteredOverridden(WifiConfiguration config)224     public static boolean isMeteredOverridden(WifiConfiguration config) {
225         return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
226     }
227 }
228