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