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