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