1 /*
2  * Copyright 2018 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.server.wifi;
18 
19 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
20 import static android.net.wifi.WifiInfo.INVALID_RSSI;
21 import static android.net.wifi.WifiInfo.LINK_SPEED_UNKNOWN;
22 
23 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS;
24 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC;
25 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_REJECTION;
26 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_TIMEOUT;
27 import static com.android.server.wifi.WifiHealthMonitor.REASON_AUTH_FAILURE;
28 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE;
29 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE_DISCONNECTION;
30 import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL;
31 import static com.android.server.wifi.WifiHealthMonitor.REASON_NO_FAILURE;
32 import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL;
33 
34 import android.annotation.IntDef;
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.content.Context;
38 import android.net.MacAddress;
39 import android.net.wifi.ScanResult;
40 import android.net.wifi.SupplicantState;
41 import android.net.wifi.WifiManager;
42 import android.util.ArrayMap;
43 import android.util.Base64;
44 import android.util.LocalLog;
45 import android.util.Log;
46 import android.util.Pair;
47 import android.util.SparseLongArray;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.util.Preconditions;
51 import com.android.server.wifi.WifiBlocklistMonitor.FailureReason;
52 import com.android.server.wifi.WifiHealthMonitor.FailureStats;
53 import com.android.server.wifi.proto.WifiScoreCardProto;
54 import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint;
55 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStats;
56 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAll;
57 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLevel;
58 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLink;
59 import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats;
60 import com.android.server.wifi.proto.WifiScoreCardProto.Event;
61 import com.android.server.wifi.proto.WifiScoreCardProto.HistogramBucket;
62 import com.android.server.wifi.proto.WifiScoreCardProto.Network;
63 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkList;
64 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkStats;
65 import com.android.server.wifi.proto.WifiScoreCardProto.SecurityType;
66 import com.android.server.wifi.proto.WifiScoreCardProto.Signal;
67 import com.android.server.wifi.proto.WifiScoreCardProto.UnivariateStatistic;
68 import com.android.server.wifi.proto.nano.WifiMetricsProto.BandwidthEstimatorStats;
69 import com.android.server.wifi.util.IntHistogram;
70 import com.android.server.wifi.util.LruList;
71 import com.android.server.wifi.util.NativeUtil;
72 import com.android.server.wifi.util.RssiUtil;
73 
74 import com.google.protobuf.ByteString;
75 import com.google.protobuf.InvalidProtocolBufferException;
76 
77 import java.io.FileDescriptor;
78 import java.io.PrintWriter;
79 import java.lang.annotation.Retention;
80 import java.lang.annotation.RetentionPolicy;
81 import java.nio.ByteBuffer;
82 import java.security.MessageDigest;
83 import java.security.NoSuchAlgorithmException;
84 import java.util.ArrayList;
85 import java.util.Iterator;
86 import java.util.List;
87 import java.util.Map;
88 import java.util.Objects;
89 import java.util.concurrent.atomic.AtomicReference;
90 import java.util.stream.Collectors;
91 
92 import javax.annotation.concurrent.NotThreadSafe;
93 
94 /**
95  * Retains statistical information about the performance of various
96  * access points and networks, as experienced by this device.
97  *
98  * The purpose is to better inform future network selection and switching
99  * by this device and help health monitor detect network issues.
100  */
101 @NotThreadSafe
102 public class WifiScoreCard {
103 
104     public static final String DUMP_ARG = "WifiScoreCard";
105 
106     private static final String TAG = "WifiScoreCard";
107     private boolean mVerboseLoggingEnabled = false;
108 
109     @VisibleForTesting
110     boolean mPersistentHistograms = true;
111 
112     private static final int TARGET_IN_MEMORY_ENTRIES = 50;
113     private static final int UNKNOWN_REASON = -1;
114 
115     public static final String PER_BSSID_DATA_NAME = "scorecard.proto";
116     public static final String PER_NETWORK_DATA_NAME = "perNetworkData";
117 
118     static final int INSUFFICIENT_RECENT_STATS = 0;
119     static final int SUFFICIENT_RECENT_STATS_ONLY = 1;
120     static final int SUFFICIENT_RECENT_PREV_STATS = 2;
121 
122     private static final int MAX_FREQUENCIES_PER_SSID = 30;
123     private static final int MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS = 6_000;
124 
125     private final Clock mClock;
126     private final String mL2KeySeed;
127     private MemoryStore mMemoryStore;
128     private final DeviceConfigFacade mDeviceConfigFacade;
129     private final Context mContext;
130     private final WifiGlobals mWifiGlobals;
131     private final LocalLog mLocalLog = new LocalLog(256);
132     private final long[][][] mL2ErrorAccPercent =
133             new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
134     private final long[][][] mBwEstErrorAccPercent =
135             new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
136     private final long[][][] mBwEstValue =
137             new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
138     private final int[][][] mBwEstCount =
139             new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
140 
141     @VisibleForTesting
142     static final int[] RSSI_BUCKETS = intsInRange(-100, -20);
143 
intsInRange(int min, int max)144     private static int[] intsInRange(int min, int max) {
145         int[] a = new int[max - min + 1];
146         for (int i = 0; i < a.length; i++) {
147             a[i] = min + i;
148         }
149         return a;
150     }
151 
152     /** Our view of the memory store */
153     public interface MemoryStore {
154         /** Requests a read, with asynchronous reply */
read(String key, String name, BlobListener blobListener)155         void read(String key, String name, BlobListener blobListener);
156         /** Requests a write, does not wait for completion */
write(String key, String name, byte[] value)157         void write(String key, String name, byte[] value);
158         /** Sets the cluster identifier */
setCluster(String key, String cluster)159         void setCluster(String key, String cluster);
160         /** Requests removal of all entries matching the cluster */
removeCluster(String cluster)161         void removeCluster(String cluster);
162     }
163     /** Asynchronous response to a read request */
164     public interface BlobListener {
165         /** Provides the previously stored value, or null if none */
onBlobRetrieved(@ullable byte[] value)166         void onBlobRetrieved(@Nullable byte[] value);
167     }
168 
169     /**
170      * Installs a memory store.
171      *
172      * Normally this happens just once, shortly after we start. But wifi can
173      * come up before the disk is ready, and we might not yet have a valid wall
174      * clock when we start up, so we need to be prepared to begin recording data
175      * even if the MemoryStore is not yet available.
176      *
177      * When the store is installed for the first time, we want to merge any
178      * recently recorded data together with data already in the store. But if
179      * the store restarts and has to be reinstalled, we don't want to do
180      * this merge, because that would risk double-counting the old data.
181      *
182      */
installMemoryStore(@onNull MemoryStore memoryStore)183     public void installMemoryStore(@NonNull MemoryStore memoryStore) {
184         Preconditions.checkNotNull(memoryStore);
185         if (mMemoryStore == null) {
186             mMemoryStore = memoryStore;
187             Log.i(TAG, "Installing MemoryStore");
188             requestReadForAllChanged();
189         } else {
190             mMemoryStore = memoryStore;
191             Log.e(TAG, "Reinstalling MemoryStore");
192             // Our caller will call doWrites() eventually, so nothing more to do here.
193         }
194     }
195 
196     /**
197      * Enable/Disable verbose logging.
198      *
199      * @param verbose true to enable and false to disable.
200      */
enableVerboseLogging(boolean verbose)201     public void enableVerboseLogging(boolean verbose) {
202         mVerboseLoggingEnabled = verbose;
203     }
204 
205     @VisibleForTesting
206     static final long TS_NONE = -1;
207 
208     /** Tracks the connection status per Wifi interface. */
209     private static final class IfaceInfo {
210         /**
211          * Timestamp of the start of the most recent connection attempt.
212          *
213          * Based on mClock.getElapsedSinceBootMillis().
214          *
215          * This is for calculating the time to connect and the duration of the connection.
216          * Any negative value means we are not currently connected.
217          */
218         public long tsConnectionAttemptStart = TS_NONE;
219 
220         /**
221          * Timestamp captured when we find out about a firmware roam
222          */
223         public long tsRoam = TS_NONE;
224 
225         /**
226          * Becomes true the first time we see a poll with a valid RSSI in a connection
227          */
228         public boolean polled = false;
229 
230         /**
231          * Records validation success for the current connection.
232          *
233          * We want to gather statistics only on the first success.
234          */
235         public boolean validatedThisConnectionAtLeastOnce = false;
236 
237         /**
238          * A note to ourself that we are attempting a network switch
239          */
240         public boolean attemptingSwitch = false;
241 
242         /**
243          *  SSID of currently connected or connecting network. Used during disconnection
244          */
245         public String ssidCurr = "";
246         /**
247          *  SSID of previously connected network. Used during disconnection when connection attempt
248          *  of current network is issued before the disconnection of previous network.
249          */
250         public String ssidPrev = "";
251         /**
252          * A flag that notes that current disconnection is not generated by wpa_supplicant
253          * which may indicate abnormal disconnection.
254          */
255         public boolean nonlocalDisconnection = false;
256         public int disconnectionReason;
257 
258         public long firmwareAlertTimeMs = TS_NONE;
259     }
260 
261     /**
262      * String key: iface name
263      * IfaceInfo value: current status of iface
264      */
265     private final Map<String, IfaceInfo> mIfaceToInfoMap = new ArrayMap<>();
266 
267     /** Gets the IfaceInfo, or create it if it doesn't exist. */
getIfaceInfo(String ifaceName)268     private IfaceInfo getIfaceInfo(String ifaceName) {
269         return mIfaceToInfoMap.computeIfAbsent(ifaceName, k -> new IfaceInfo());
270     }
271 
272     /**
273      * @param clock is the time source
274      * @param l2KeySeed is for making our L2Keys usable only on this device
275      */
WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, Context context, WifiGlobals wifiGlobals)276     public WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade,
277             Context context, WifiGlobals wifiGlobals) {
278         mClock = clock;
279         mContext = context;
280         mL2KeySeed = l2KeySeed;
281         mPlaceholderPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS));
282         mPlaceholderPerNetwork = new PerNetwork("");
283         mDeviceConfigFacade = deviceConfigFacade;
284         mWifiGlobals = wifiGlobals;
285     }
286 
287     /**
288      * Gets the L2Key and GroupHint associated with the connection.
289      */
getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo)290     public @NonNull Pair<String, String> getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo) {
291         PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
292         if (perBssid == mPlaceholderPerBssid) {
293             return new Pair<>(null, null);
294         }
295         return new Pair<>(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid));
296     }
297 
298     /**
299      * Computes the GroupHint associated with the given ssid.
300      */
groupHintFromSsid(String ssid)301     public @NonNull String groupHintFromSsid(String ssid) {
302         final long groupIdHash = computeHashLong(ssid, mPlaceholderPerBssid.bssid, mL2KeySeed);
303         return groupHintFromLong(groupIdHash);
304     }
305 
306     /** Handle network disconnection. */
resetConnectionState(String ifaceName)307     public void resetConnectionState(String ifaceName) {
308         IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
309         noteDisconnectionForIface(ifaceInfo);
310         resetConnectionStateForIfaceInternal(ifaceInfo, true);
311     }
312 
313     /** Handle shutdown event. */
resetAllConnectionStates()314     public void resetAllConnectionStates() {
315         for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
316             noteDisconnectionForIface(ifaceInfo);
317             resetConnectionStateForIfaceInternal(ifaceInfo, true);
318         }
319     }
320 
noteDisconnectionForIface(IfaceInfo ifaceInfo)321     private void noteDisconnectionForIface(IfaceInfo ifaceInfo) {
322         String ssidDisconnected = ifaceInfo.attemptingSwitch
323                 ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr;
324         updatePerNetwork(Event.DISCONNECTION, ssidDisconnected, INVALID_RSSI, LINK_SPEED_UNKNOWN,
325                 UNKNOWN_REASON, ifaceInfo);
326         if (mVerboseLoggingEnabled && ifaceInfo.tsConnectionAttemptStart > TS_NONE
327                 && !ifaceInfo.attemptingSwitch) {
328             Log.v(TAG, "handleNetworkDisconnect", new Exception());
329         }
330     }
331 
resetAllConnectionStatesInternal()332     private void resetAllConnectionStatesInternal() {
333         for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
334             resetConnectionStateForIfaceInternal(ifaceInfo, false);
335         }
336     }
337 
338     /**
339      * @param calledFromResetConnectionState says the call is from outside the class,
340      *        indicating that we need to respect the value of mAttemptingSwitch.
341      */
resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo, boolean calledFromResetConnectionState)342     private void resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo,
343             boolean calledFromResetConnectionState) {
344         if (!calledFromResetConnectionState) {
345             ifaceInfo.attemptingSwitch = false;
346         }
347         if (!ifaceInfo.attemptingSwitch) {
348             ifaceInfo.tsConnectionAttemptStart = TS_NONE;
349         }
350         ifaceInfo.tsRoam = TS_NONE;
351         ifaceInfo.polled = false;
352         ifaceInfo.validatedThisConnectionAtLeastOnce = false;
353         ifaceInfo.nonlocalDisconnection = false;
354         ifaceInfo.firmwareAlertTimeMs = TS_NONE;
355     }
356 
357     /**
358      * Updates perBssid using relevant parts of WifiInfo
359      *
360      * @param wifiInfo object holding relevant values.
361      */
updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo)362     private void updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo) {
363         PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
364         perBssid.updateEventStats(event,
365                 wifiInfo.getFrequency(),
366                 wifiInfo.getRssi(),
367                 wifiInfo.getLinkSpeed(),
368                 wifiInfo.getIfaceName());
369         perBssid.setNetworkConfigId(wifiInfo.getNetworkId());
370         logd("BSSID update " + event + " ID: " + perBssid.id + " " + wifiInfo);
371     }
372 
373     /**
374      * Updates perNetwork with SSID, current RSSI and failureReason. failureReason is  meaningful
375      * only during connection failure.
376      */
updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)377     private void updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi,
378             int txSpeed, int failureReason, IfaceInfo ifaceInfo) {
379         PerNetwork perNetwork = lookupNetwork(ssid);
380         logd("network update " + event + ((ssid == null) ? " " : " "
381                     + ssid) + " ID: " + perNetwork.id + " RSSI " + rssi + " txSpeed " + txSpeed);
382         perNetwork.updateEventStats(event, rssi, txSpeed, failureReason, ifaceInfo);
383     }
384 
385     /**
386      * Updates the score card after a signal poll
387      *
388      * @param wifiInfo object holding relevant values
389      */
noteSignalPoll(@onNull ExtendedWifiInfo wifiInfo)390     public void noteSignalPoll(@NonNull ExtendedWifiInfo wifiInfo) {
391         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
392         if (!ifaceInfo.polled && wifiInfo.getRssi() != INVALID_RSSI) {
393             updatePerBssid(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo);
394             ifaceInfo.polled = true;
395         }
396         updatePerBssid(Event.SIGNAL_POLL, wifiInfo);
397         int validTxSpeed = geTxLinkSpeedWithSufficientTxRate(wifiInfo);
398         updatePerNetwork(Event.SIGNAL_POLL, wifiInfo.getSSID(), wifiInfo.getRssi(),
399                 validTxSpeed, UNKNOWN_REASON, ifaceInfo);
400         if (ifaceInfo.tsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) {
401             long duration = mClock.getElapsedSinceBootMillis() - ifaceInfo.tsRoam;
402             if (duration >= SUCCESS_MILLIS_SINCE_ROAM) {
403                 updatePerBssid(Event.ROAM_SUCCESS, wifiInfo);
404                 ifaceInfo.tsRoam = TS_NONE;
405                 doWritesBssid();
406             }
407         }
408     }
409 
geTxLinkSpeedWithSufficientTxRate(@onNull ExtendedWifiInfo wifiInfo)410     private int geTxLinkSpeedWithSufficientTxRate(@NonNull ExtendedWifiInfo wifiInfo) {
411         int txRate = (int) Math.ceil(wifiInfo.getSuccessfulTxPacketsPerSecond()
412                 + wifiInfo.getLostTxPacketsPerSecond()
413                 + wifiInfo.getRetriedTxPacketsPerSecond());
414         int txSpeed = wifiInfo.getTxLinkSpeedMbps();
415         logd("txRate: " + txRate + " txSpeed: " + txSpeed);
416         return (txRate >= HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC) ? txSpeed : LINK_SPEED_UNKNOWN;
417     }
418 
419     /** Wait a few seconds before considering the roam successful */
420     private static final long SUCCESS_MILLIS_SINCE_ROAM = 4_000;
421 
422     /**
423      * Updates the score card after IP configuration
424      *
425      * @param wifiInfo object holding relevant values
426      */
noteIpConfiguration(@onNull ExtendedWifiInfo wifiInfo)427     public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) {
428         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
429         updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
430         updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(),
431                 wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo);
432         PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID());
433         perNetwork.initBandwidthFilter(wifiInfo);
434         ifaceInfo.attemptingSwitch = false;
435         doWrites();
436     }
437 
438     /**
439      * Updates the score card after network validation success.
440      *
441      * @param wifiInfo object holding relevant values
442      */
noteValidationSuccess(@onNull ExtendedWifiInfo wifiInfo)443     public void noteValidationSuccess(@NonNull ExtendedWifiInfo wifiInfo) {
444         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
445         if (ifaceInfo.validatedThisConnectionAtLeastOnce) return; // Only once per connection
446         updatePerBssid(Event.VALIDATION_SUCCESS, wifiInfo);
447         ifaceInfo.validatedThisConnectionAtLeastOnce = true;
448         doWrites();
449     }
450 
451     /**
452      * Updates the score card after network validation failure
453      *
454      * @param wifiInfo object holding relevant values
455      */
noteValidationFailure(@onNull ExtendedWifiInfo wifiInfo)456     public void noteValidationFailure(@NonNull ExtendedWifiInfo wifiInfo) {
457         // VALIDATION_FAILURE is not currently recorded.
458     }
459 
460     /**
461      * Records the start of a connection attempt
462      *
463      * @param wifiInfo may have state about an existing connection
464      * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache
465      * @param ssid is the network SSID of connection attempt
466      */
noteConnectionAttempt(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid)467     public void noteConnectionAttempt(@NonNull ExtendedWifiInfo wifiInfo,
468             int scanRssi, String ssid) {
469         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
470         // We may or may not be currently connected. If not, simply record the start.
471         // But if we are connected, wrap up the old one first.
472         if (ifaceInfo.tsConnectionAttemptStart > TS_NONE) {
473             if (ifaceInfo.polled) {
474                 updatePerBssid(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo);
475             }
476             ifaceInfo.attemptingSwitch = true;
477         }
478         ifaceInfo.tsConnectionAttemptStart = mClock.getElapsedSinceBootMillis();
479         ifaceInfo.polled = false;
480         ifaceInfo.ssidPrev = ifaceInfo.ssidCurr;
481         ifaceInfo.ssidCurr = ssid;
482         ifaceInfo.firmwareAlertTimeMs = TS_NONE;
483 
484         updatePerNetwork(Event.CONNECTION_ATTEMPT, ssid, scanRssi, LINK_SPEED_UNKNOWN,
485                 UNKNOWN_REASON, ifaceInfo);
486         logd("CONNECTION_ATTEMPT" + (ifaceInfo.attemptingSwitch ? " X " : " ") + wifiInfo);
487     }
488 
489     /**
490      * Records a newly assigned NetworkAgent netId.
491      */
noteNetworkAgentCreated(@onNull ExtendedWifiInfo wifiInfo, int networkAgentId)492     public void noteNetworkAgentCreated(@NonNull ExtendedWifiInfo wifiInfo, int networkAgentId) {
493         PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
494         logd("NETWORK_AGENT_ID: " + networkAgentId + " ID: " + perBssid.id);
495         perBssid.mNetworkAgentId = networkAgentId;
496     }
497 
498     /**
499      * Record disconnection not initiated by wpa_supplicant in connected mode
500      * @param reason is detailed disconnection reason code
501      */
noteNonlocalDisconnect(String ifaceName, int reason)502     public void noteNonlocalDisconnect(String ifaceName, int reason) {
503         IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
504 
505         ifaceInfo.nonlocalDisconnection = true;
506         ifaceInfo.disconnectionReason = reason;
507         logd("nonlocal disconnection with reason: " + reason);
508     }
509 
510     /**
511      * Record firmware alert timestamp and error code
512      */
noteFirmwareAlert(int errorCode)513     public void noteFirmwareAlert(int errorCode) {
514         long ts = mClock.getElapsedSinceBootMillis();
515         // Firmware alert is device-level, not per-iface. Thus, note firmware alert on all ifaces.
516         for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) {
517             ifaceInfo.firmwareAlertTimeMs = ts;
518         }
519         logd("firmware alert with error code: " + errorCode);
520     }
521 
522     /**
523      * Updates the score card after a failed connection attempt
524      *
525      * @param wifiInfo object holding relevant values.
526      * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache
527      * @param ssid is the network SSID.
528      * @param failureReason is connection failure reason
529      */
noteConnectionFailure(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid, @FailureReason int failureReason)530     public void noteConnectionFailure(@NonNull ExtendedWifiInfo wifiInfo,
531             int scanRssi, String ssid, @FailureReason int failureReason) {
532         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
533         // TODO: add the breakdown of level2FailureReason
534         updatePerBssid(Event.CONNECTION_FAILURE, wifiInfo);
535         updatePerNetwork(Event.CONNECTION_FAILURE, ssid, scanRssi, LINK_SPEED_UNKNOWN,
536                 failureReason, ifaceInfo);
537         resetConnectionStateForIfaceInternal(ifaceInfo, false);
538     }
539 
540     /**
541      * Updates the score card after network reachability failure
542      *
543      * @param wifiInfo object holding relevant values
544      */
noteIpReachabilityLost(@onNull ExtendedWifiInfo wifiInfo)545     public void noteIpReachabilityLost(@NonNull ExtendedWifiInfo wifiInfo) {
546         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
547         if (ifaceInfo.tsRoam > TS_NONE) {
548             ifaceInfo.tsConnectionAttemptStart = ifaceInfo.tsRoam; // just to update elapsed
549             updatePerBssid(Event.ROAM_FAILURE, wifiInfo);
550         } else {
551             updatePerBssid(Event.IP_REACHABILITY_LOST, wifiInfo);
552         }
553         // No need to call resetConnectionStateInternal() because
554         // resetConnectionState() will be called after WifiNative.disconnect() in ClientModeImpl
555         doWrites();
556     }
557 
558     /**
559      * Updates the score card before a roam
560      *
561      * We may have already done a firmware roam, but wifiInfo has not yet
562      * been updated, so we still have the old state.
563      *
564      * @param wifiInfo object holding relevant values
565      */
noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo)566     private void noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo) {
567         updatePerBssid(Event.LAST_POLL_BEFORE_ROAM, wifiInfo);
568         ifaceInfo.tsRoam = mClock.getElapsedSinceBootMillis();
569     }
570 
571     /**
572      * Called when the supplicant state is about to change, before wifiInfo is updated
573      *
574      * @param wifiInfo object holding old values
575      * @param state the new supplicant state
576      */
noteSupplicantStateChanging(@onNull ExtendedWifiInfo wifiInfo, SupplicantState state)577     public void noteSupplicantStateChanging(@NonNull ExtendedWifiInfo wifiInfo,
578             SupplicantState state) {
579         IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName());
580         if (state == SupplicantState.COMPLETED && wifiInfo.getSupplicantState() == state) {
581             // Our signal that a firmware roam has occurred
582             noteRoam(ifaceInfo, wifiInfo);
583         }
584         logd("Changing state to " + state + " " + wifiInfo);
585     }
586 
587     /**
588      * Called after the supplicant state changed
589      *
590      * @param wifiInfo object holding old values
591      */
noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo)592     public void noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo) {
593         logd("ifaceName=" + wifiInfo.getIfaceName() + ",wifiInfo=" + wifiInfo);
594     }
595 
596     /**
597      * Updates the score card when wifi is disabled
598      *
599      * @param wifiInfo object holding relevant values
600      */
noteWifiDisabled(@onNull ExtendedWifiInfo wifiInfo)601     public void noteWifiDisabled(@NonNull ExtendedWifiInfo wifiInfo) {
602         updatePerBssid(Event.WIFI_DISABLED, wifiInfo);
603     }
604 
605     /**
606      * Records the last successful L2 connection timestamp for a BSSID.
607      * @return the previous BSSID connection time.
608      */
setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs)609     public long setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs) {
610         PerBssid perBssid = lookupBssid(ssid, bssid);
611         long prev = perBssid.lastConnectionTimestampMs;
612         perBssid.lastConnectionTimestampMs = timeMs;
613         return prev;
614     }
615 
616     /**
617      * Returns the last successful L2 connection time for this BSSID.
618      */
getBssidConnectionTimestampMs(String ssid, String bssid)619     public long getBssidConnectionTimestampMs(String ssid, String bssid) {
620         return lookupBssid(ssid, bssid).lastConnectionTimestampMs;
621     }
622 
623     /**
624      * Increment the blocklist streak count for a failure reason on an AP.
625      * @return the updated count
626      */
incrementBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)627     public int incrementBssidBlocklistStreak(String ssid, String bssid,
628             @WifiBlocklistMonitor.FailureReason int reason) {
629         PerBssid perBssid = lookupBssid(ssid, bssid);
630         return ++perBssid.blocklistStreakCount[reason];
631     }
632 
633     /**
634      * Get the blocklist streak count for a failure reason on an AP.
635      * @return the blocklist streak count
636      */
getBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)637     public int getBssidBlocklistStreak(String ssid, String bssid,
638             @WifiBlocklistMonitor.FailureReason int reason) {
639         return lookupBssid(ssid, bssid).blocklistStreakCount[reason];
640     }
641 
642     /**
643      * Clear the blocklist streak count for a failure reason on an AP.
644      */
resetBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)645     public void resetBssidBlocklistStreak(String ssid, String bssid,
646             @WifiBlocklistMonitor.FailureReason int reason) {
647         lookupBssid(ssid, bssid).blocklistStreakCount[reason] = 0;
648     }
649 
650     /**
651      * Clear the blocklist streak count for all APs that belong to this SSID.
652      */
resetBssidBlocklistStreakForSsid(@onNull String ssid)653     public void resetBssidBlocklistStreakForSsid(@NonNull String ssid) {
654         Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator();
655         while (it.hasNext()) {
656             PerBssid perBssid = it.next().getValue();
657             if (!ssid.equals(perBssid.ssid)) {
658                 continue;
659             }
660             for (int i = 0; i < perBssid.blocklistStreakCount.length; i++) {
661                 perBssid.blocklistStreakCount[i] = 0;
662             }
663         }
664     }
665 
666     /**
667      * Detect abnormal disconnection at high RSSI with a high rate
668      */
detectAbnormalDisconnection(String ifaceName)669     public int detectAbnormalDisconnection(String ifaceName) {
670         IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
671         String ssid = ifaceInfo.attemptingSwitch ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr;
672         PerNetwork perNetwork = lookupNetwork(ssid);
673         NetworkConnectionStats recentStats = perNetwork.getRecentStats();
674         if (recentStats.getRecentCountCode() == CNT_SHORT_CONNECTION_NONLOCAL) {
675             return detectAbnormalFailureReason(recentStats, CNT_SHORT_CONNECTION_NONLOCAL,
676                     REASON_SHORT_CONNECTION_NONLOCAL,
677                     mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(),
678                     mDeviceConfigFacade.getShortConnectionNonlocalCountMin(),
679                     CNT_DISCONNECTION);
680         } else if (recentStats.getRecentCountCode() == CNT_DISCONNECTION_NONLOCAL) {
681             return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL,
682                     REASON_DISCONNECTION_NONLOCAL,
683                     mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(),
684                     mDeviceConfigFacade.getDisconnectionNonlocalCountMin(),
685                     CNT_DISCONNECTION);
686         } else {
687             return REASON_NO_FAILURE;
688         }
689     }
690 
691     /**
692      * Detect abnormal connection failure at high RSSI with a high rate
693      */
detectAbnormalConnectionFailure(String ssid)694     public int detectAbnormalConnectionFailure(String ssid) {
695         PerNetwork perNetwork = lookupNetwork(ssid);
696         NetworkConnectionStats recentStats = perNetwork.getRecentStats();
697         int recentCountCode = recentStats.getRecentCountCode();
698         if (recentCountCode == CNT_AUTHENTICATION_FAILURE) {
699             return detectAbnormalFailureReason(recentStats, CNT_AUTHENTICATION_FAILURE,
700                     REASON_AUTH_FAILURE,
701                     mDeviceConfigFacade.getAuthFailureHighThrPercent(),
702                     mDeviceConfigFacade.getAuthFailureCountMin(),
703                     CNT_CONNECTION_ATTEMPT);
704         } else if (recentCountCode == CNT_ASSOCIATION_REJECTION) {
705             return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_REJECTION,
706                     REASON_ASSOC_REJECTION,
707                     mDeviceConfigFacade.getAssocRejectionHighThrPercent(),
708                     mDeviceConfigFacade.getAssocRejectionCountMin(),
709                     CNT_CONNECTION_ATTEMPT);
710         } else if (recentCountCode == CNT_ASSOCIATION_TIMEOUT) {
711             return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_TIMEOUT,
712                     REASON_ASSOC_TIMEOUT,
713                     mDeviceConfigFacade.getAssocTimeoutHighThrPercent(),
714                     mDeviceConfigFacade.getAssocTimeoutCountMin(),
715                     CNT_CONNECTION_ATTEMPT);
716         } else if (recentCountCode == CNT_DISCONNECTION_NONLOCAL_CONNECTING) {
717             return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
718                     REASON_CONNECTION_FAILURE_DISCONNECTION,
719                     mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(),
720                     mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
721                     CNT_CONNECTION_ATTEMPT);
722         } else if (recentCountCode == CNT_CONNECTION_FAILURE) {
723             return detectAbnormalFailureReason(recentStats, CNT_CONNECTION_FAILURE,
724                     REASON_CONNECTION_FAILURE,
725                     mDeviceConfigFacade.getConnectionFailureHighThrPercent(),
726                     mDeviceConfigFacade.getConnectionFailureCountMin(),
727                     CNT_CONNECTION_ATTEMPT);
728         } else {
729             return REASON_NO_FAILURE;
730         }
731     }
732 
detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)733     private int detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode,
734             int reasonCode, int highThresholdPercent, int minCount, int refCountCode) {
735         // To detect abnormal failure which may trigger bugReport,
736         // increase the detection threshold by thresholdRatio
737         int thresholdRatio =
738                 mDeviceConfigFacade.getBugReportThresholdExtraRatio();
739         if (isHighPercentageAndEnoughCount(stats, countCode, reasonCode,
740                 highThresholdPercent * thresholdRatio,
741                 minCount * thresholdRatio,
742                 refCountCode)) {
743             return reasonCode;
744         } else {
745             return REASON_NO_FAILURE;
746         }
747     }
748 
isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)749     private boolean isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode,
750             int reasonCode, int highThresholdPercent, int minCount, int refCountCode) {
751         highThresholdPercent = Math.min(highThresholdPercent, 100);
752         // Use Laplace's rule of succession, useful especially for a small
753         // connection attempt count
754         // R = (f+1)/(n+2) with a pseudo count of 2 (one for f and one for s)
755         return ((stats.getCount(countCode) >= minCount)
756                 && ((stats.getCount(countCode) + 1) * 100)
757                 >= (highThresholdPercent * (stats.getCount(refCountCode) + 2)));
758     }
759 
760     final class PerBssid extends MemoryStoreAccessBase {
761         public int id;
762         public final String ssid;
763         public final MacAddress bssid;
764         public final int[] blocklistStreakCount =
765                 new int[WifiBlocklistMonitor.NUMBER_REASON_CODES];
766         public long[][][] bandwidthStatsValue =
767                 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
768         public int[][][] bandwidthStatsCount =
769                 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
770         // The wall clock time in milliseconds for the last successful l2 connection.
771         public long lastConnectionTimestampMs;
772         public boolean changed;
773         public boolean referenced;
774 
775         private SecurityType mSecurityType = null;
776         private int mNetworkAgentId = Integer.MIN_VALUE;
777         private int mNetworkConfigId = Integer.MIN_VALUE;
778         private final Map<Pair<Event, Integer>, PerSignal>
779                 mSignalForEventAndFrequency = new ArrayMap<>();
780 
PerBssid(String ssid, MacAddress bssid)781         PerBssid(String ssid, MacAddress bssid) {
782             super(computeHashLong(ssid, bssid, mL2KeySeed));
783             this.ssid = ssid;
784             this.bssid = bssid;
785             this.id = idFromLong();
786             this.changed = false;
787             this.referenced = false;
788         }
updateEventStats(Event event, int frequency, int rssi, int linkspeed, String ifaceName)789         void updateEventStats(Event event, int frequency, int rssi, int linkspeed,
790                 String ifaceName) {
791             PerSignal perSignal = lookupSignal(event, frequency);
792             if (rssi != INVALID_RSSI) {
793                 perSignal.rssi.update(rssi);
794                 changed = true;
795             }
796             if (linkspeed > 0) {
797                 perSignal.linkspeed.update(linkspeed);
798                 changed = true;
799             }
800             IfaceInfo ifaceInfo = getIfaceInfo(ifaceName);
801             if (perSignal.elapsedMs != null && ifaceInfo.tsConnectionAttemptStart > TS_NONE) {
802                 long millis =
803                         mClock.getElapsedSinceBootMillis() - ifaceInfo.tsConnectionAttemptStart;
804                 if (millis >= 0) {
805                     perSignal.elapsedMs.update(millis);
806                     changed = true;
807                 }
808             }
809         }
lookupSignal(Event event, int frequency)810         PerSignal lookupSignal(Event event, int frequency) {
811             finishPendingRead();
812             Pair<Event, Integer> key = new Pair<>(event, frequency);
813             PerSignal ans = mSignalForEventAndFrequency.get(key);
814             if (ans == null) {
815                 ans = new PerSignal(event, frequency);
816                 mSignalForEventAndFrequency.put(key, ans);
817             }
818             return ans;
819         }
getSecurityType()820         SecurityType getSecurityType() {
821             finishPendingRead();
822             return mSecurityType;
823         }
setSecurityType(SecurityType securityType)824         void setSecurityType(SecurityType securityType) {
825             finishPendingRead();
826             if (!Objects.equals(securityType, mSecurityType)) {
827                 mSecurityType = securityType;
828                 changed = true;
829             }
830         }
setNetworkConfigId(int networkConfigId)831         void setNetworkConfigId(int networkConfigId) {
832             // Not serialized, so don't need to set changed, etc.
833             if (networkConfigId >= 0) {
834                 mNetworkConfigId = networkConfigId;
835             }
836         }
toAccessPoint()837         AccessPoint toAccessPoint() {
838             return toAccessPoint(false);
839         }
toAccessPoint(boolean obfuscate)840         AccessPoint toAccessPoint(boolean obfuscate) {
841             finishPendingRead();
842             AccessPoint.Builder builder = AccessPoint.newBuilder();
843             builder.setId(id);
844             if (!obfuscate) {
845                 builder.setBssid(ByteString.copyFrom(bssid.toByteArray()));
846             }
847             if (mSecurityType != null) {
848                 builder.setSecurityType(mSecurityType);
849             }
850             for (PerSignal sig: mSignalForEventAndFrequency.values()) {
851                 builder.addEventStats(sig.toSignal());
852             }
853             builder.setBandwidthStatsAll(toBandwidthStatsAll(
854                     bandwidthStatsValue, bandwidthStatsCount));
855             return builder.build();
856         }
merge(AccessPoint ap)857         PerBssid merge(AccessPoint ap) {
858             if (ap.hasId() && this.id != ap.getId()) {
859                 return this;
860             }
861             if (ap.hasSecurityType()) {
862                 SecurityType prev = ap.getSecurityType();
863                 if (mSecurityType == null) {
864                     mSecurityType = prev;
865                 } else if (!mSecurityType.equals(prev)) {
866                     if (mVerboseLoggingEnabled) {
867                         Log.i(TAG, "ID: " + id
868                                 + "SecurityType changed: " + prev + " to " + mSecurityType);
869                     }
870                     changed = true;
871                 }
872             }
873             for (Signal signal: ap.getEventStatsList()) {
874                 Pair<Event, Integer> key = new Pair<>(signal.getEvent(), signal.getFrequency());
875                 PerSignal perSignal = mSignalForEventAndFrequency.get(key);
876                 if (perSignal == null) {
877                     mSignalForEventAndFrequency.put(key,
878                             new PerSignal(key.first, key.second).merge(signal));
879                     // No need to set changed for this, since we are in sync with what's stored
880                 } else {
881                     perSignal.merge(signal);
882                     changed = true;
883                 }
884             }
885             if (ap.hasBandwidthStatsAll()) {
886                 mergeBandwidthStatsAll(ap.getBandwidthStatsAll(),
887                         bandwidthStatsValue, bandwidthStatsCount);
888             }
889             return this;
890         }
891 
892         /**
893          * Handles (when convenient) the arrival of previously stored data.
894          *
895          * The response from IpMemoryStore arrives on a different thread, so we
896          * defer handling it until here, when we're on our favorite thread and
897          * in a good position to deal with it. We may have already collected some
898          * data before now, so we need to be prepared to merge the new and old together.
899          */
finishPendingRead()900         void finishPendingRead() {
901             final byte[] serialized = finishPendingReadBytes();
902             if (serialized == null) return;
903             AccessPoint ap;
904             try {
905                 ap = AccessPoint.parseFrom(serialized);
906             } catch (InvalidProtocolBufferException e) {
907                 Log.e(TAG, "Failed to deserialize", e);
908                 return;
909             }
910             merge(ap);
911         }
912 
913         /**
914          * Estimates the probability of getting internet access, based on the
915          * device experience.
916          *
917          * @return a probability, expressed as a percentage in the range 0 to 100
918          */
estimatePercentInternetAvailability()919         public int estimatePercentInternetAvailability() {
920             // Initialize counts accoring to Laplace's rule of succession
921             int trials = 2;
922             int successes = 1;
923             // Aggregate over all of the frequencies
924             for (PerSignal s : mSignalForEventAndFrequency.values()) {
925                 switch (s.event) {
926                     case IP_CONFIGURATION_SUCCESS:
927                         if (s.elapsedMs != null) {
928                             trials += s.elapsedMs.count;
929                         }
930                         break;
931                     case VALIDATION_SUCCESS:
932                         if (s.elapsedMs != null) {
933                             successes += s.elapsedMs.count;
934                         }
935                         break;
936                     default:
937                         break;
938                 }
939             }
940             // Note that because of roaming it is possible to count successes
941             // without corresponding trials.
942             return Math.min(Math.max(Math.round(successes * 100.0f / trials), 0), 100);
943         }
944     }
945 
toBandwidthStatsAll(long[][][] values, int[][][] counts)946     private BandwidthStatsAll toBandwidthStatsAll(long[][][] values, int[][][] counts) {
947         BandwidthStatsAll.Builder builder = BandwidthStatsAll.newBuilder();
948         builder.setStats2G(toBandwidthStatsAllLink(values[0], counts[0]));
949         builder.setStatsAbove2G(toBandwidthStatsAllLink(values[1], counts[1]));
950         return builder.build();
951     }
952 
toBandwidthStatsAllLink(long[][] values, int[][] counts)953     private BandwidthStatsAllLink toBandwidthStatsAllLink(long[][] values, int[][] counts) {
954         BandwidthStatsAllLink.Builder builder = BandwidthStatsAllLink.newBuilder();
955         builder.setTx(toBandwidthStatsAllLevel(values[LINK_TX], counts[LINK_TX]));
956         builder.setRx(toBandwidthStatsAllLevel(values[LINK_RX], counts[LINK_RX]));
957         return builder.build();
958     }
959 
toBandwidthStatsAllLevel(long[] values, int[] counts)960     private BandwidthStatsAllLevel toBandwidthStatsAllLevel(long[] values, int[] counts) {
961         BandwidthStatsAllLevel.Builder builder = BandwidthStatsAllLevel.newBuilder();
962         for (int i = 0; i < NUM_SIGNAL_LEVEL; i++) {
963             builder.addLevel(toBandwidthStats(values[i], counts[i]));
964         }
965         return builder.build();
966     }
967 
toBandwidthStats(long value, int count)968     private BandwidthStats toBandwidthStats(long value, int count) {
969         BandwidthStats.Builder builder = BandwidthStats.newBuilder();
970         builder.setValue(value);
971         builder.setCount(count);
972         return builder.build();
973     }
974 
mergeBandwidthStatsAll(BandwidthStatsAll source, long[][][] values, int[][][] counts)975     private void mergeBandwidthStatsAll(BandwidthStatsAll source,
976             long[][][] values, int[][][] counts) {
977         if (source.hasStats2G()) {
978             mergeBandwidthStatsAllLink(source.getStats2G(), values[0], counts[0]);
979         }
980         if (source.hasStatsAbove2G()) {
981             mergeBandwidthStatsAllLink(source.getStatsAbove2G(), values[1], counts[1]);
982         }
983     }
984 
mergeBandwidthStatsAllLink(BandwidthStatsAllLink source, long[][] values, int[][] counts)985     private void mergeBandwidthStatsAllLink(BandwidthStatsAllLink source,
986             long[][] values, int[][] counts) {
987         if (source.hasTx()) {
988             mergeBandwidthStatsAllLevel(source.getTx(), values[LINK_TX], counts[LINK_TX]);
989         }
990         if (source.hasRx()) {
991             mergeBandwidthStatsAllLevel(source.getRx(), values[LINK_RX], counts[LINK_RX]);
992         }
993     }
994 
mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source, long[] values, int[] counts)995     private void mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source,
996             long[] values, int[] counts) {
997         int levelCnt = source.getLevelCount();
998         for (int i = 0; i < levelCnt; i++) {
999             BandwidthStats stats = source.getLevel(i);
1000             if (stats.hasValue()) {
1001                 values[i] += stats.getValue();
1002             }
1003             if (stats.hasCount()) {
1004                 counts[i] += stats.getCount();
1005             }
1006         }
1007     }
1008 
1009     // TODO: b/178641307 move the following parameters to config.xml
1010     // Array dimension : int [NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]
1011     static final int[][][] LINK_BANDWIDTH_INIT_KBPS =
1012             {{{500, 2500, 10000, 12000, 12000}, {500, 2500, 10000, 30000, 30000}},
1013             {{1500, 7500, 12000, 12000, 12000}, {1500, 7500, 30000, 60000, 60000}}};
1014     // To be used in link bandwidth estimation, each TrafficStats poll sample needs to be above
1015     // the following values. Defined per signal level.
1016     // int [NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]
1017     // Use the low Tx threshold because xDSL UL speed could be below 1Mbps.
1018     static final int[][] LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE =
1019             {{200, 300, 300, 300, 300}, {200, 500, 1000, 2000, 2000}};
1020     // To be used in the long term avg, each count needs to be above the following value
1021     static final int BANDWIDTH_STATS_COUNT_THR = 5;
1022     private static final int TIME_CONSTANT_SMALL_SEC = 6;
1023     // If RSSI changes by more than the below value, update BW filter with small time constant
1024     private static final int RSSI_DELTA_THR_DB = 8;
1025     private static final int FILTER_SCALE = 128;
1026     // Force weight to 0 if the elapsed time is above LARGE_TIME_DECAY_RATIO * time constant
1027     private static final int LARGE_TIME_DECAY_RATIO = 4;
1028     // Used to derive byte count threshold from avg BW
1029     private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 6;
1030     private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8;
1031     // For some high speed connections, heavy DL traffic could falsely trigger UL BW update due to
1032     // TCP ACK and the low Tx byte count threshold. To work around the issue, skip Tx BW update if
1033     // Rx Bytes / Tx Bytes > RX_OVER_TX_BYTE_RATIO_MAX (heavy DL and light UL traffic)
1034     private static final int RX_OVER_TX_BYTE_RATIO_MAX = 5;
1035     // radio on time below the following value is ignored.
1036     static final int RADIO_ON_TIME_MIN_MS = 200;
1037     static final int RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS = 200;
1038     static final int NUM_SIGNAL_LEVEL = 5;
1039     static final int LINK_TX = 0;
1040     static final int LINK_RX = 1;
1041     private static final int NUM_LINK_BAND = 2;
1042     private static final int NUM_LINK_DIRECTION = 2;
1043     private static final long BW_UPDATE_TIME_RESET_MS = TIME_CONSTANT_SMALL_SEC * 1000 * -10;
1044     private static final int MAX_ERROR_PERCENT = 100 * 100;
1045     private static final int EXTRA_SAMPLE_BW_FILTERING = 2;
1046     /**
1047      * A class collecting the connection and link bandwidth stats of one network or SSID.
1048      */
1049     final class PerNetwork extends MemoryStoreAccessBase {
1050         public int id;
1051         public final String ssid;
1052         public boolean changed;
1053         private int mLastRssiPoll = INVALID_RSSI;
1054         private int mLastTxSpeedPoll = LINK_SPEED_UNKNOWN;
1055         private long mLastRssiPollTimeMs = TS_NONE;
1056         private long mConnectionSessionStartTimeMs = TS_NONE;
1057         private NetworkConnectionStats mRecentStats;
1058         private NetworkConnectionStats mStatsCurrBuild;
1059         private NetworkConnectionStats mStatsPrevBuild;
1060         private LruList<Integer> mFrequencyList;
1061         // In memory keep frequency with timestamp last time available, the elapsed time since boot.
1062         private SparseLongArray mFreqTimestamp;
1063         private long mLastRxBytes;
1064         private long mLastTxBytes;
1065         private boolean mLastTrafficValid = true;
1066         private String mBssid = "";
1067         private int mSignalLevel;  // initialize to zero to handle possible race condition
1068         private int mBandIdx;  // initialize to zero to handle possible race condition
1069         private int[] mByteDeltaAccThr = new int[NUM_LINK_DIRECTION];
1070         private int[] mFilterKbps = new int[NUM_LINK_DIRECTION];
1071         private int[] mBandwidthSampleKbps = new int[NUM_LINK_DIRECTION];
1072         private int[] mAvgUsedKbps = new int[NUM_LINK_DIRECTION];
1073         private int mBandwidthUpdateRssiDbm = -1;
1074         private int mBandwidthUpdateBandIdx = -1;
1075         private boolean[] mBandwidthSampleValid = new boolean[NUM_LINK_DIRECTION];
1076         private long[] mBandwidthSampleValidTimeMs = new long[]{BW_UPDATE_TIME_RESET_MS,
1077                 BW_UPDATE_TIME_RESET_MS};
1078         long[][][] mBandwidthStatsValue =
1079                 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
1080         int[][][] mBandwidthStatsCount =
1081                 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL];
1082 
PerNetwork(String ssid)1083         PerNetwork(String ssid) {
1084             super(computeHashLong(ssid, MacAddress.fromString(DEFAULT_MAC_ADDRESS), mL2KeySeed));
1085             this.ssid = ssid;
1086             this.id = idFromLong();
1087             this.changed = false;
1088             mRecentStats = new NetworkConnectionStats();
1089             mStatsCurrBuild = new NetworkConnectionStats();
1090             mStatsPrevBuild = new NetworkConnectionStats();
1091             mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID);
1092             mFreqTimestamp = new SparseLongArray();
1093         }
1094 
updateEventStats(Event event, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)1095         void updateEventStats(Event event, int rssi, int txSpeed, int failureReason,
1096                 IfaceInfo ifaceInfo) {
1097             finishPendingRead();
1098             long currTimeMs = mClock.getElapsedSinceBootMillis();
1099             switch (event) {
1100                 case SIGNAL_POLL:
1101                     mLastRssiPoll = rssi;
1102                     mLastRssiPollTimeMs = currTimeMs;
1103                     mLastTxSpeedPoll = txSpeed;
1104                     changed = true;
1105                     break;
1106                 case CONNECTION_ATTEMPT:
1107                     logd(" scan rssi: " + rssi);
1108                     if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) {
1109                         mRecentStats.incrementCount(CNT_CONNECTION_ATTEMPT);
1110                     }
1111                     mConnectionSessionStartTimeMs = currTimeMs;
1112                     changed = true;
1113                     break;
1114                 case CONNECTION_FAILURE:
1115                     mConnectionSessionStartTimeMs = TS_NONE;
1116                     if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) {
1117                         if (failureReason != WifiBlocklistMonitor.REASON_WRONG_PASSWORD) {
1118                             mRecentStats.incrementCount(CNT_CONNECTION_FAILURE);
1119                             mRecentStats.incrementCount(CNT_CONSECUTIVE_CONNECTION_FAILURE);
1120                         }
1121                         switch (failureReason) {
1122                             case WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
1123                                 mRecentStats.incrementCount(CNT_ASSOCIATION_REJECTION);
1124                                 break;
1125                             case WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
1126                                 mRecentStats.incrementCount(CNT_ASSOCIATION_TIMEOUT);
1127                                 break;
1128                             case WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
1129                             case WifiBlocklistMonitor.REASON_EAP_FAILURE:
1130                                 mRecentStats.incrementCount(CNT_AUTHENTICATION_FAILURE);
1131                                 break;
1132                             case WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING:
1133                                 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING);
1134                                 break;
1135                             case WifiBlocklistMonitor.REASON_WRONG_PASSWORD:
1136                                 mRecentStats.incrementCount(CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE);
1137                                 break;
1138                             case WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
1139                             case WifiBlocklistMonitor.REASON_DHCP_FAILURE:
1140                             default:
1141                                 break;
1142                         }
1143                     }
1144                     changed = true;
1145                     break;
1146                 case IP_CONFIGURATION_SUCCESS:
1147                     // Reset CNT_CONSECUTIVE_CONNECTION_FAILURE since L3 is also connected
1148                     mRecentStats.clearCount(CNT_CONSECUTIVE_CONNECTION_FAILURE);
1149                     mRecentStats.clearCount(CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE);
1150                     changed = true;
1151                     logd(this.toString());
1152                     break;
1153                 case WIFI_DISABLED:
1154                 case DISCONNECTION:
1155                     if (mConnectionSessionStartTimeMs <= TS_NONE) {
1156                         return;
1157                     }
1158                     handleDisconnectionAfterConnection(ifaceInfo);
1159                     mConnectionSessionStartTimeMs = TS_NONE;
1160                     mLastRssiPollTimeMs = TS_NONE;
1161                     mFilterKbps[LINK_TX] = 0;
1162                     mFilterKbps[LINK_RX] = 0;
1163                     mBandwidthUpdateRssiDbm = -1;
1164                     mBandwidthUpdateBandIdx = -1;
1165                     changed = true;
1166                     break;
1167                 default:
1168                     break;
1169             }
1170         }
1171         @Override
toString()1172         public String toString() {
1173             StringBuilder sb = new StringBuilder();
1174             sb.append("SSID: ").append(ssid).append("\n");
1175             if (mLastRssiPollTimeMs != TS_NONE) {
1176                 sb.append(" LastRssiPollTime: ");
1177                 sb.append(mLastRssiPollTimeMs);
1178             }
1179             sb.append(" LastRssiPoll: " + mLastRssiPoll);
1180             sb.append(" LastTxSpeedPoll: " + mLastTxSpeedPoll);
1181             sb.append("\n");
1182             sb.append(" StatsRecent: ").append(mRecentStats).append("\n");
1183             sb.append(" StatsCurr: ").append(mStatsCurrBuild).append("\n");
1184             sb.append(" StatsPrev: ").append(mStatsPrevBuild);
1185             sb.append(" BandwidthStats:\n");
1186             for (int i = 0; i < NUM_LINK_BAND; i++) {
1187                 for (int j = 0; j < NUM_LINK_DIRECTION; j++) {
1188                     sb.append(" avgKbps: ");
1189                     for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
1190                         int avgKbps = mBandwidthStatsCount[i][j][k] == 0 ? 0 : (int)
1191                                 (mBandwidthStatsValue[i][j][k] / mBandwidthStatsCount[i][j][k]);
1192                         sb.append(" " + avgKbps);
1193                     }
1194                     sb.append("\n count: ");
1195                     for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
1196                         sb.append(" " + mBandwidthStatsCount[i][j][k]);
1197                     }
1198                     sb.append("\n");
1199                 }
1200                 sb.append("\n");
1201             }
1202             return sb.toString();
1203         }
1204 
handleDisconnectionAfterConnection(IfaceInfo ifaceInfo)1205         private void handleDisconnectionAfterConnection(IfaceInfo ifaceInfo) {
1206             long currTimeMs = mClock.getElapsedSinceBootMillis();
1207             int currSessionDurationMs = (int) (currTimeMs - mConnectionSessionStartTimeMs);
1208             int currSessionDurationSec = currSessionDurationMs / 1000;
1209             mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec);
1210             long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs;
1211             boolean hasRecentRssiPoll = mLastRssiPollTimeMs > TS_NONE
1212                     && timeSinceLastRssiPollMs <= mDeviceConfigFacade
1213                     .getHealthMonitorRssiPollValidTimeMs();
1214             if (hasRecentRssiPoll) {
1215                 mRecentStats.incrementCount(CNT_DISCONNECTION);
1216             }
1217             int fwAlertValidTimeMs = mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs();
1218             long timeSinceLastFirmAlert = currTimeMs - ifaceInfo.firmwareAlertTimeMs;
1219             boolean isInvalidFwAlertTime = ifaceInfo.firmwareAlertTimeMs == TS_NONE;
1220             boolean disableFwAlertCheck = fwAlertValidTimeMs == -1;
1221             boolean passFirmwareAlertCheck = disableFwAlertCheck ? true : (isInvalidFwAlertTime
1222                     ? false : timeSinceLastFirmAlert < fwAlertValidTimeMs);
1223             boolean hasHighRssiOrHighTxSpeed =
1224                     mLastRssiPoll >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()
1225                     || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS;
1226             if (ifaceInfo.nonlocalDisconnection && hasRecentRssiPoll
1227                     && isAbnormalDisconnectionReason(ifaceInfo.disconnectionReason)
1228                     && passFirmwareAlertCheck
1229                     && hasHighRssiOrHighTxSpeed) {
1230                 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL);
1231                 if (currSessionDurationMs <= mDeviceConfigFacade
1232                         .getHealthMonitorShortConnectionDurationThrMs()) {
1233                     mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL);
1234                 }
1235             }
1236 
1237         }
1238 
isAbnormalDisconnectionReason(int disconnectionReason)1239         private boolean isAbnormalDisconnectionReason(int disconnectionReason) {
1240             long mask = mDeviceConfigFacade.getAbnormalDisconnectionReasonCodeMask();
1241             return disconnectionReason >= 0 && disconnectionReason <= 63
1242                     && ((mask >> disconnectionReason) & 0x1) == 0x1;
1243         }
1244 
getRecentStats()1245         @NonNull NetworkConnectionStats getRecentStats() {
1246             return mRecentStats;
1247         }
getStatsCurrBuild()1248         @NonNull NetworkConnectionStats getStatsCurrBuild() {
1249             return mStatsCurrBuild;
1250         }
getStatsPrevBuild()1251         @NonNull NetworkConnectionStats getStatsPrevBuild() {
1252             return mStatsPrevBuild;
1253         }
1254 
1255         /**
1256          * Retrieve the list of frequencies seen for this network, with the most recent first.
1257          * @param ageInMills Max age to filter the channels.
1258          * @return a list of frequencies
1259          */
getFrequencies(Long ageInMills)1260         List<Integer> getFrequencies(Long ageInMills) {
1261             List<Integer> results = new ArrayList<>();
1262             Long nowInMills = mClock.getElapsedSinceBootMillis();
1263             for (Integer freq : mFrequencyList.getEntries()) {
1264                 if (nowInMills - mFreqTimestamp.get(freq, 0L) > ageInMills) {
1265                     continue;
1266                 }
1267                 results.add(freq);
1268             }
1269             return results;
1270         }
1271 
1272         /**
1273          * Add a frequency to the list of frequencies for this network.
1274          * Will evict the least recently added frequency if the cache is full.
1275          */
addFrequency(int frequency)1276         void addFrequency(int frequency) {
1277             mFrequencyList.add(frequency);
1278             mFreqTimestamp.put(frequency, mClock.getElapsedSinceBootMillis());
1279         }
1280 
1281         /**
1282          * Update link bandwidth estimates based on TrafficStats byte counts and radio on time
1283          */
updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes)1284         void updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats,
1285                 ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes) {
1286             mBandwidthSampleValid[LINK_TX] = false;
1287             mBandwidthSampleValid[LINK_RX] = false;
1288             // Sometimes TrafficStats byte counts return invalid values
1289             // Ignore next two polls if it happens
1290             boolean trafficValid = txBytes >= mLastTxBytes && rxBytes >= mLastRxBytes;
1291             if (!mLastTrafficValid || !trafficValid) {
1292                 mLastTrafficValid = trafficValid;
1293                 logv("invalid traffic count tx " + txBytes + " last " + mLastTxBytes
1294                         + " rx " + rxBytes + " last " + mLastRxBytes);
1295                 mLastTxBytes = txBytes;
1296                 mLastRxBytes = rxBytes;
1297                 return;
1298             }
1299 
1300             updateWifiInfo(wifiInfo);
1301             updateLinkBandwidthTxRxSample(oldStats, newStats, wifiInfo, txBytes, rxBytes);
1302             mLastTxBytes = txBytes;
1303             mLastRxBytes = rxBytes;
1304 
1305             updateBandwidthWithFilterApplied(LINK_TX, wifiInfo);
1306             updateBandwidthWithFilterApplied(LINK_RX, wifiInfo);
1307             mBandwidthUpdateRssiDbm = wifiInfo.getRssi();
1308             mBandwidthUpdateBandIdx = mBandIdx;
1309         }
1310 
updateWifiInfo(ExtendedWifiInfo wifiInfo)1311         void updateWifiInfo(ExtendedWifiInfo wifiInfo) {
1312             int rssi = wifiInfo.getRssi();
1313             mSignalLevel = RssiUtil.calculateSignalLevel(mContext, rssi);
1314             mSignalLevel = Math.min(mSignalLevel, NUM_SIGNAL_LEVEL - 1);
1315             mBandIdx = getBandIdx(wifiInfo);
1316             mBssid = wifiInfo.getBSSID();
1317             mByteDeltaAccThr[LINK_TX] = getByteDeltaAccThr(LINK_TX);
1318             mByteDeltaAccThr[LINK_RX] = getByteDeltaAccThr(LINK_RX);
1319         }
1320 
updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes)1321         private void updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats,
1322                 WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo,
1323                 long txBytes, long rxBytes) {
1324             // oldStats is reset to null after screen off or disconnection
1325             if (oldStats == null || newStats == null) {
1326                 return;
1327             }
1328 
1329             int elapsedTimeMs = (int) (newStats.timeStampInMs - oldStats.timeStampInMs);
1330             if (elapsedTimeMs > MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS) {
1331                 return;
1332             }
1333 
1334             int onTimeMs = getTotalRadioOnTimeMs(newStats) - getTotalRadioOnTimeMs(oldStats);
1335             if (onTimeMs <= RADIO_ON_TIME_MIN_MS
1336                     || onTimeMs > RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS + elapsedTimeMs) {
1337                 return;
1338             }
1339             onTimeMs = Math.min(elapsedTimeMs, onTimeMs);
1340 
1341             long txBytesDelta = txBytes - mLastTxBytes;
1342             long rxBytesDelta = rxBytes - mLastRxBytes;
1343             int txLinkSpeedMbps = wifiInfo.getTxLinkSpeedMbps();
1344             int txBandwidthCapMbps =  (txLinkSpeedMbps == LINK_SPEED_UNKNOWN)
1345                     ? wifiInfo.getMaxSupportedTxLinkSpeedMbps() : txLinkSpeedMbps;
1346             if (txBytesDelta * RX_OVER_TX_BYTE_RATIO_MAX >= rxBytesDelta) {
1347                 updateBandwidthSample(txBytesDelta, LINK_TX, onTimeMs, txBandwidthCapMbps);
1348             }
1349 
1350             int rxLinkSpeedMbps = wifiInfo.getRxLinkSpeedMbps();
1351             // Rx link speed is not available in many devices. In these cases, fall back to Tx link
1352             // speed which is available in most devices.
1353             int rxBandwidthCapMbps = (rxLinkSpeedMbps == LINK_SPEED_UNKNOWN)
1354                     ? txBandwidthCapMbps : rxLinkSpeedMbps;
1355             updateBandwidthSample(rxBytesDelta, LINK_RX, onTimeMs, rxBandwidthCapMbps);
1356 
1357             if (!mBandwidthSampleValid[LINK_RX] && !mBandwidthSampleValid[LINK_TX]) {
1358                 return;
1359             }
1360             StringBuilder sb = new StringBuilder();
1361             logv(sb.append(" rssi ").append(wifiInfo.getRssi())
1362                     .append(" level ").append(mSignalLevel)
1363                     .append(" bssid ").append(wifiInfo.getBSSID())
1364                     .append(" freq ").append(wifiInfo.getFrequency())
1365                     .append(" onTimeMs ").append(onTimeMs)
1366                     .append(" txKB ").append(txBytesDelta / 1024)
1367                     .append(" rxKB ").append(rxBytesDelta / 1024)
1368                     .append(" txKBThr ").append(mByteDeltaAccThr[LINK_TX] / 1024)
1369                     .append(" rxKBThr ").append(mByteDeltaAccThr[LINK_RX] / 1024)
1370                     .toString());
1371         }
1372 
getTotalRadioOnTimeMs(@onNull WifiLinkLayerStats stats)1373         private int getTotalRadioOnTimeMs(@NonNull WifiLinkLayerStats stats) {
1374             if (stats.radioStats != null && stats.radioStats.length > 0) {
1375                 int totalRadioOnTime = 0;
1376                 for (WifiLinkLayerStats.RadioStat stat : stats.radioStats) {
1377                     totalRadioOnTime += stat.on_time;
1378                 }
1379                 return totalRadioOnTime;
1380             }
1381             return stats.on_time;
1382         }
1383 
getBandIdx(ExtendedWifiInfo wifiInfo)1384         private int getBandIdx(ExtendedWifiInfo wifiInfo) {
1385             return ScanResult.is24GHz(wifiInfo.getFrequency()) ? 0 : 1;
1386         }
1387 
updateBandwidthSample(long bytesDelta, int link, int onTimeMs, int bandwidthCapMbps)1388         private void updateBandwidthSample(long bytesDelta, int link, int onTimeMs,
1389                 int bandwidthCapMbps) {
1390             if (bytesDelta < mByteDeltaAccThr[link]) {
1391                 return;
1392             }
1393             long speedKbps = bytesDelta / onTimeMs * 8;
1394             if (speedKbps > (bandwidthCapMbps * 1000)) {
1395                 return;
1396             }
1397             int linkBandwidthKbps = (int) speedKbps;
1398             changed = true;
1399             mBandwidthSampleValid[link] = true;
1400             mBandwidthSampleKbps[link] = linkBandwidthKbps;
1401             // Update SSID level stats
1402             mBandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps;
1403             mBandwidthStatsCount[mBandIdx][link][mSignalLevel]++;
1404             // Update BSSID level stats
1405             PerBssid perBssid = lookupBssid(ssid, mBssid);
1406             if (perBssid != mPlaceholderPerBssid) {
1407                 perBssid.changed = true;
1408                 perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps;
1409                 perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]++;
1410             }
1411         }
1412 
getByteDeltaAccThr(int link)1413         private int getByteDeltaAccThr(int link) {
1414             int maxTimeDeltaMs = mWifiGlobals.getPollRssiIntervalMillis();
1415             int lowBytes = calculateByteCountThreshold(getAvgUsedLinkBandwidthKbps(link),
1416                     maxTimeDeltaMs);
1417             // Start with a predefined value
1418             int deltaAccThr = LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE[link][mSignalLevel] * 1024;
1419             if (lowBytes > 0) {
1420                 // Raise the threshold if the avg usage BW is high
1421                 deltaAccThr = Math.max(lowBytes, deltaAccThr);
1422                 deltaAccThr = Math.min(deltaAccThr, mDeviceConfigFacade
1423                         .getTrafficStatsThresholdMaxKbyte() * 1024);
1424             }
1425             return deltaAccThr;
1426         }
1427 
initBandwidthFilter(ExtendedWifiInfo wifiInfo)1428         private void initBandwidthFilter(ExtendedWifiInfo wifiInfo) {
1429             updateWifiInfo(wifiInfo);
1430             for (int link = 0; link < NUM_LINK_DIRECTION; link++) {
1431                 mFilterKbps[link] = getAvgLinkBandwidthKbps(link);
1432             }
1433         }
1434 
updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo)1435         private void updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo) {
1436             int avgKbps = getAvgLinkBandwidthKbps(link);
1437             // Feed the filter with the long term avg if there is no valid BW sample so that filter
1438             // will gradually converge the long term avg.
1439             int filterInKbps = mBandwidthSampleValid[link] ? mBandwidthSampleKbps[link] : avgKbps;
1440 
1441             long currTimeMs = mClock.getElapsedSinceBootMillis();
1442             int timeDeltaSec = (int) ((currTimeMs - mBandwidthSampleValidTimeMs[link]) / 1000);
1443 
1444             // If the operation condition changes since the last valid sample or the current sample
1445             // has higher BW, use a faster filter. Otherwise, use a slow filter
1446             int timeConstantSec;
1447             if (Math.abs(mBandwidthUpdateRssiDbm - wifiInfo.getRssi()) > RSSI_DELTA_THR_DB
1448                     || (mBandwidthSampleValid[link] && mBandwidthSampleKbps[link] > avgKbps)
1449                     || mBandwidthUpdateBandIdx != mBandIdx) {
1450                 timeConstantSec = TIME_CONSTANT_SMALL_SEC;
1451             } else {
1452                 timeConstantSec = mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec();
1453             }
1454             // Update timestamp for next iteration
1455             if (mBandwidthSampleValid[link]) {
1456                 mBandwidthSampleValidTimeMs[link] = currTimeMs;
1457             }
1458 
1459             if (filterInKbps == mFilterKbps[link]) {
1460                 return;
1461             }
1462             int alpha = timeDeltaSec > LARGE_TIME_DECAY_RATIO * timeConstantSec ? 0
1463                     : (int) (FILTER_SCALE * Math.exp(-1.0 * timeDeltaSec / timeConstantSec));
1464 
1465             if (alpha == 0) {
1466                 mFilterKbps[link] = filterInKbps;
1467                 return;
1468             }
1469             long filterOutKbps = (long) mFilterKbps[link] * alpha
1470                     + filterInKbps * FILTER_SCALE - filterInKbps * alpha;
1471             filterOutKbps = filterOutKbps / FILTER_SCALE;
1472             mFilterKbps[link] = (int) Math.min(filterOutKbps, Integer.MAX_VALUE);
1473             StringBuilder sb = new StringBuilder();
1474             logd(sb.append(link)
1475                     .append(" lastSampleWeight=").append(alpha)
1476                     .append("/").append(FILTER_SCALE)
1477                     .append(" filterInKbps=").append(filterInKbps)
1478                     .append(" avgKbps=").append(avgKbps)
1479                     .append(" filterOutKbps=").append(mFilterKbps[link])
1480                     .toString());
1481         }
1482 
getAvgLinkBandwidthKbps(int link)1483         private int getAvgLinkBandwidthKbps(int link) {
1484             mAvgUsedKbps[link] = getAvgUsedLinkBandwidthKbps(link);
1485             if (mAvgUsedKbps[link] > 0) {
1486                 return mAvgUsedKbps[link];
1487             }
1488 
1489             int avgBwAdjSignalKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps(link);
1490             if (avgBwAdjSignalKbps > 0) {
1491                 return avgBwAdjSignalKbps;
1492             }
1493 
1494             // Fall back to a cold-start value
1495             return LINK_BANDWIDTH_INIT_KBPS[mBandIdx][link][mSignalLevel];
1496         }
1497 
getAvgUsedLinkBandwidthKbps(int link)1498         private int getAvgUsedLinkBandwidthKbps(int link) {
1499             // Check if current BSSID/signal level has enough count
1500             PerBssid perBssid = lookupBssid(ssid, mBssid);
1501             int count = perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel];
1502             long value = perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel];
1503             if (count >= BANDWIDTH_STATS_COUNT_THR) {
1504                 return (int) (value / count);
1505             }
1506 
1507             // Check if current SSID/band/signal level has enough count
1508             count = mBandwidthStatsCount[mBandIdx][link][mSignalLevel];
1509             value = mBandwidthStatsValue[mBandIdx][link][mSignalLevel];
1510             if (count >= BANDWIDTH_STATS_COUNT_THR) {
1511                 return (int) (value / count);
1512             }
1513 
1514             return -1;
1515         }
1516 
getAvgUsedBandwidthAdjacentThreeLevelKbps(int link)1517         private int getAvgUsedBandwidthAdjacentThreeLevelKbps(int link) {
1518             int count = 0;
1519             long value = 0;
1520             for (int i = -1; i <= 1; i++) {
1521                 int currLevel = mSignalLevel + i;
1522                 if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) {
1523                     continue;
1524                 }
1525                 count += mBandwidthStatsCount[mBandIdx][link][currLevel];
1526                 value += mBandwidthStatsValue[mBandIdx][link][currLevel];
1527             }
1528             if (count >= BANDWIDTH_STATS_COUNT_THR) {
1529                 return (int) (value / count);
1530             }
1531 
1532             return -1;
1533         }
1534 
1535         // Calculate a byte count threshold for the given avg BW and observation window size
calculateByteCountThreshold(int avgBwKbps, int durationMs)1536         private int calculateByteCountThreshold(int avgBwKbps, int durationMs) {
1537             long avgBytes = (long) avgBwKbps / 8 * durationMs;
1538             long result = avgBytes * LOW_BW_TO_AVG_BW_RATIO_NUM / LOW_BW_TO_AVG_BW_RATIO_DEN;
1539             return (int) Math.min(result, Integer.MAX_VALUE);
1540         }
1541 
1542         /**
1543          * Get the latest TrafficStats based end-to-end Tx link bandwidth estimation in Kbps
1544          */
getTxLinkBandwidthKbps()1545         public int getTxLinkBandwidthKbps() {
1546             return (mFilterKbps[LINK_TX] > 0) ? mFilterKbps[LINK_TX]
1547                     : getAvgLinkBandwidthKbps(LINK_TX);
1548         }
1549 
1550         /**
1551          * Get the latest TrafficStats based end-to-end Rx link bandwidth estimation in Kbps
1552          */
getRxLinkBandwidthKbps()1553         public int getRxLinkBandwidthKbps() {
1554             return (mFilterKbps[LINK_RX] > 0) ? mFilterKbps[LINK_RX]
1555                     : getAvgLinkBandwidthKbps(LINK_RX);
1556         }
1557 
1558         /**
1559          * Update Bandwidth metrics with the latest reported bandwidth and L2 BW values
1560          */
updateBwMetrics(int[] reportedKbps, int[] l2Kbps)1561         public void updateBwMetrics(int[] reportedKbps, int[] l2Kbps) {
1562             for (int link = 0; link < NUM_LINK_DIRECTION; link++) {
1563                 calculateError(link, reportedKbps[link], l2Kbps[link]);
1564             }
1565         }
1566 
calculateError(int link, int reportedKbps, int l2Kbps)1567         private void calculateError(int link, int reportedKbps, int l2Kbps) {
1568             if (mBandwidthStatsCount[mBandIdx][link][mSignalLevel] < (BANDWIDTH_STATS_COUNT_THR
1569                     + EXTRA_SAMPLE_BW_FILTERING) || !mBandwidthSampleValid[link]
1570                     || mAvgUsedKbps[link] <= 0) {
1571                 return;
1572             }
1573             int bwSampleKbps = mBandwidthSampleKbps[link];
1574             int bwEstExtErrPercent = calculateErrorPercent(reportedKbps, bwSampleKbps);
1575             int bwEstIntErrPercent = calculateErrorPercent(mFilterKbps[link], bwSampleKbps);
1576             int l2ErrPercent = calculateErrorPercent(l2Kbps, bwSampleKbps);
1577             mBwEstErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(bwEstExtErrPercent);
1578             mL2ErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(l2ErrPercent);
1579             mBwEstValue[mBandIdx][link][mSignalLevel] += bwSampleKbps;
1580             mBwEstCount[mBandIdx][link][mSignalLevel]++;
1581             StringBuilder sb = new StringBuilder();
1582             logv(sb.append(link)
1583                     .append(" sampKbps ").append(bwSampleKbps)
1584                     .append(" filtKbps ").append(mFilterKbps[link])
1585                     .append(" reportedKbps ").append(reportedKbps)
1586                     .append(" avgUsedKbps ").append(mAvgUsedKbps[link])
1587                     .append(" l2Kbps ").append(l2Kbps)
1588                     .append(" intErrPercent ").append(bwEstIntErrPercent)
1589                     .append(" extErrPercent ").append(bwEstExtErrPercent)
1590                     .append(" l2ErrPercent ").append(l2ErrPercent)
1591                     .toString());
1592         }
1593 
calculateErrorPercent(int inKbps, int bwSampleKbps)1594         private int calculateErrorPercent(int inKbps, int bwSampleKbps) {
1595             long errorPercent = 100L * (inKbps - bwSampleKbps) / bwSampleKbps;
1596             return (int) Math.max(-MAX_ERROR_PERCENT, Math.min(errorPercent, MAX_ERROR_PERCENT));
1597         }
1598 
1599         /**
1600         /* Detect a significant failure stats change with historical data
1601         /* or high failure stats without historical data.
1602         /* @return 0 if recentStats doesn't have sufficient data
1603          *         1 if recentStats has sufficient data while statsPrevBuild doesn't
1604          *         2 if recentStats and statsPrevBuild have sufficient data
1605          */
dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1606         int dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh) {
1607             finishPendingRead();
1608             dailyDetectionDisconnectionEvent(statsDec, statsInc, statsHigh);
1609             return dailyDetectionConnectionEvent(statsDec, statsInc, statsHigh);
1610         }
1611 
dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1612         private int dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc,
1613                 FailureStats statsHigh) {
1614             // Skip daily detection if recentStats is not sufficient
1615             if (!isRecentConnectionStatsSufficient()) return INSUFFICIENT_RECENT_STATS;
1616             if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT)
1617                     < mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt()) {
1618                 // don't have enough historical data,
1619                 // so only detect high failure stats without relying on mStatsPrevBuild.
1620                 recentStatsHighDetectionConnection(statsHigh);
1621                 return SUFFICIENT_RECENT_STATS_ONLY;
1622             } else {
1623                 // mStatsPrevBuild has enough updates,
1624                 // detect improvement or degradation
1625                 statsDeltaDetectionConnection(statsDec, statsInc);
1626                 return SUFFICIENT_RECENT_PREV_STATS;
1627             }
1628         }
1629 
dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1630         private void dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc,
1631                 FailureStats statsHigh) {
1632             // Skip daily detection if recentStats is not sufficient
1633             int minConnectAttempt = mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt();
1634             if (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) {
1635                 return;
1636             }
1637             if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) {
1638                 recentStatsHighDetectionDisconnection(statsHigh);
1639             } else {
1640                 statsDeltaDetectionDisconnection(statsDec, statsInc);
1641             }
1642         }
1643 
statsDeltaDetectionConnection(FailureStats statsDec, FailureStats statsInc)1644         private void statsDeltaDetectionConnection(FailureStats statsDec,
1645                 FailureStats statsInc) {
1646             statsDeltaDetection(statsDec, statsInc, CNT_CONNECTION_FAILURE,
1647                     REASON_CONNECTION_FAILURE,
1648                     mDeviceConfigFacade.getConnectionFailureCountMin(),
1649                     CNT_CONNECTION_ATTEMPT);
1650             statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
1651                     REASON_CONNECTION_FAILURE_DISCONNECTION,
1652                     mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
1653                     CNT_CONNECTION_ATTEMPT);
1654             statsDeltaDetection(statsDec, statsInc, CNT_AUTHENTICATION_FAILURE,
1655                     REASON_AUTH_FAILURE,
1656                     mDeviceConfigFacade.getAuthFailureCountMin(),
1657                     CNT_CONNECTION_ATTEMPT);
1658             statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_REJECTION,
1659                     REASON_ASSOC_REJECTION,
1660                     mDeviceConfigFacade.getAssocRejectionCountMin(),
1661                     CNT_CONNECTION_ATTEMPT);
1662             statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_TIMEOUT,
1663                     REASON_ASSOC_TIMEOUT,
1664                     mDeviceConfigFacade.getAssocTimeoutCountMin(),
1665                     CNT_CONNECTION_ATTEMPT);
1666         }
1667 
recentStatsHighDetectionConnection(FailureStats statsHigh)1668         private void recentStatsHighDetectionConnection(FailureStats statsHigh) {
1669             recentStatsHighDetection(statsHigh, CNT_CONNECTION_FAILURE,
1670                     REASON_CONNECTION_FAILURE,
1671                     mDeviceConfigFacade.getConnectionFailureHighThrPercent(),
1672                     mDeviceConfigFacade.getConnectionFailureCountMin(),
1673                     CNT_CONNECTION_ATTEMPT);
1674             recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL_CONNECTING,
1675                     REASON_CONNECTION_FAILURE_DISCONNECTION,
1676                     mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(),
1677                     mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(),
1678                     CNT_CONNECTION_ATTEMPT);
1679             recentStatsHighDetection(statsHigh, CNT_AUTHENTICATION_FAILURE,
1680                     REASON_AUTH_FAILURE,
1681                     mDeviceConfigFacade.getAuthFailureHighThrPercent(),
1682                     mDeviceConfigFacade.getAuthFailureCountMin(),
1683                     CNT_CONNECTION_ATTEMPT);
1684             recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_REJECTION,
1685                     REASON_ASSOC_REJECTION,
1686                     mDeviceConfigFacade.getAssocRejectionHighThrPercent(),
1687                     mDeviceConfigFacade.getAssocRejectionCountMin(),
1688                     CNT_CONNECTION_ATTEMPT);
1689             recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_TIMEOUT,
1690                     REASON_ASSOC_TIMEOUT,
1691                     mDeviceConfigFacade.getAssocTimeoutHighThrPercent(),
1692                     mDeviceConfigFacade.getAssocTimeoutCountMin(),
1693                     CNT_CONNECTION_ATTEMPT);
1694         }
1695 
statsDeltaDetectionDisconnection(FailureStats statsDec, FailureStats statsInc)1696         private void statsDeltaDetectionDisconnection(FailureStats statsDec,
1697                 FailureStats statsInc) {
1698             statsDeltaDetection(statsDec, statsInc, CNT_SHORT_CONNECTION_NONLOCAL,
1699                     REASON_SHORT_CONNECTION_NONLOCAL,
1700                     mDeviceConfigFacade.getShortConnectionNonlocalCountMin(),
1701                     CNT_CONNECTION_ATTEMPT);
1702             statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL,
1703                     REASON_DISCONNECTION_NONLOCAL,
1704                     mDeviceConfigFacade.getDisconnectionNonlocalCountMin(),
1705                     CNT_CONNECTION_ATTEMPT);
1706         }
1707 
recentStatsHighDetectionDisconnection(FailureStats statsHigh)1708         private void recentStatsHighDetectionDisconnection(FailureStats statsHigh) {
1709             recentStatsHighDetection(statsHigh, CNT_SHORT_CONNECTION_NONLOCAL,
1710                     REASON_SHORT_CONNECTION_NONLOCAL,
1711                     mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(),
1712                     mDeviceConfigFacade.getShortConnectionNonlocalCountMin(),
1713                     CNT_DISCONNECTION);
1714             recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL,
1715                     REASON_DISCONNECTION_NONLOCAL,
1716                     mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(),
1717                     mDeviceConfigFacade.getDisconnectionNonlocalCountMin(),
1718                     CNT_DISCONNECTION);
1719         }
1720 
statsDeltaDetection(FailureStats statsDec, FailureStats statsInc, int countCode, int reasonCode, int minCount, int refCountCode)1721         private boolean statsDeltaDetection(FailureStats statsDec,
1722                 FailureStats statsInc, int countCode, int reasonCode,
1723                 int minCount, int refCountCode) {
1724             if (isRatioAboveThreshold(mRecentStats, mStatsPrevBuild, countCode, refCountCode)
1725                     && mRecentStats.getCount(countCode) >= minCount) {
1726                 statsInc.incrementCount(reasonCode);
1727                 return true;
1728             }
1729 
1730             if (isRatioAboveThreshold(mStatsPrevBuild, mRecentStats, countCode, refCountCode)
1731                     && mStatsPrevBuild.getCount(countCode) >= minCount) {
1732                 statsDec.incrementCount(reasonCode);
1733                 return true;
1734             }
1735             return false;
1736         }
1737 
recentStatsHighDetection(FailureStats statsHigh, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)1738         private boolean recentStatsHighDetection(FailureStats statsHigh, int countCode,
1739                 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) {
1740             if (isHighPercentageAndEnoughCount(mRecentStats, countCode, reasonCode,
1741                     highThresholdPercent, minCount, refCountCode)) {
1742                 statsHigh.incrementCount(reasonCode);
1743                 return true;
1744             }
1745             return false;
1746         }
1747 
isRatioAboveThreshold(NetworkConnectionStats stats1, NetworkConnectionStats stats2, @ConnectionCountCode int countCode, int refCountCode)1748         private boolean isRatioAboveThreshold(NetworkConnectionStats stats1,
1749                 NetworkConnectionStats stats2,
1750                 @ConnectionCountCode int countCode, int refCountCode) {
1751             // Also with Laplace's rule of succession discussed above
1752             // R1 = (stats1(countCode) + 1) / (stats1(refCountCode) + 2)
1753             // R2 = (stats2(countCode) + 1) / (stats2(refCountCode) + 2)
1754             // Check R1 / R2 >= ratioThr
1755             return ((stats1.getCount(countCode) + 1) * (stats2.getCount(refCountCode) + 2)
1756                     * mDeviceConfigFacade.HEALTH_MONITOR_RATIO_THR_DENOMINATOR)
1757                     >= ((stats1.getCount(refCountCode) + 2) * (stats2.getCount(countCode) + 1)
1758                     * mDeviceConfigFacade.getHealthMonitorRatioThrNumerator());
1759         }
1760 
isRecentConnectionStatsSufficient()1761         private boolean isRecentConnectionStatsSufficient() {
1762             return (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT)
1763                 >= mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt());
1764         }
1765 
1766         // Update StatsCurrBuild with recentStats and clear recentStats
updateAfterDailyDetection()1767         void updateAfterDailyDetection() {
1768             // Skip update if recentStats is not sufficient since daily detection is also skipped
1769             if (!isRecentConnectionStatsSufficient()) return;
1770             mStatsCurrBuild.accumulateAll(mRecentStats);
1771             mRecentStats.clear();
1772             changed = true;
1773         }
1774 
1775         // Refresh StatsPrevBuild with StatsCurrBuild which is cleared afterwards
updateAfterSwBuildChange()1776         void updateAfterSwBuildChange() {
1777             finishPendingRead();
1778             mStatsPrevBuild.copy(mStatsCurrBuild);
1779             mRecentStats.clear();
1780             mStatsCurrBuild.clear();
1781             changed = true;
1782         }
1783 
toNetworkStats()1784         NetworkStats toNetworkStats() {
1785             finishPendingRead();
1786             NetworkStats.Builder builder = NetworkStats.newBuilder();
1787             builder.setId(id);
1788             builder.setRecentStats(toConnectionStats(mRecentStats));
1789             builder.setStatsCurrBuild(toConnectionStats(mStatsCurrBuild));
1790             builder.setStatsPrevBuild(toConnectionStats(mStatsPrevBuild));
1791             if (mFrequencyList.size() > 0) {
1792                 builder.addAllFrequencies(mFrequencyList.getEntries());
1793             }
1794             builder.setBandwidthStatsAll(toBandwidthStatsAll(
1795                     mBandwidthStatsValue, mBandwidthStatsCount));
1796             return builder.build();
1797         }
1798 
toConnectionStats(NetworkConnectionStats stats)1799         private ConnectionStats toConnectionStats(NetworkConnectionStats stats) {
1800             ConnectionStats.Builder builder = ConnectionStats.newBuilder();
1801             builder.setNumConnectionAttempt(stats.getCount(CNT_CONNECTION_ATTEMPT));
1802             builder.setNumConnectionFailure(stats.getCount(CNT_CONNECTION_FAILURE));
1803             builder.setConnectionDurationSec(stats.getCount(CNT_CONNECTION_DURATION_SEC));
1804             builder.setNumDisconnectionNonlocal(stats.getCount(CNT_DISCONNECTION_NONLOCAL));
1805             builder.setNumDisconnection(stats.getCount(CNT_DISCONNECTION));
1806             builder.setNumShortConnectionNonlocal(stats.getCount(CNT_SHORT_CONNECTION_NONLOCAL));
1807             builder.setNumAssociationRejection(stats.getCount(CNT_ASSOCIATION_REJECTION));
1808             builder.setNumAssociationTimeout(stats.getCount(CNT_ASSOCIATION_TIMEOUT));
1809             builder.setNumAuthenticationFailure(stats.getCount(CNT_AUTHENTICATION_FAILURE));
1810             builder.setNumDisconnectionNonlocalConnecting(
1811                     stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING));
1812             return builder.build();
1813         }
1814 
finishPendingRead()1815         void finishPendingRead() {
1816             final byte[] serialized = finishPendingReadBytes();
1817             if (serialized == null) return;
1818             NetworkStats ns;
1819             try {
1820                 ns = NetworkStats.parseFrom(serialized);
1821             } catch (InvalidProtocolBufferException e) {
1822                 Log.e(TAG, "Failed to deserialize", e);
1823                 return;
1824             }
1825             mergeNetworkStatsFromMemory(ns);
1826             changed = true;
1827         }
1828 
mergeNetworkStatsFromMemory(@onNull NetworkStats ns)1829         PerNetwork mergeNetworkStatsFromMemory(@NonNull NetworkStats ns) {
1830             if (ns.hasId() && this.id != ns.getId()) {
1831                 return this;
1832             }
1833             if (ns.hasRecentStats()) {
1834                 ConnectionStats recentStats = ns.getRecentStats();
1835                 mergeConnectionStats(recentStats, mRecentStats);
1836             }
1837             if (ns.hasStatsCurrBuild()) {
1838                 ConnectionStats statsCurr = ns.getStatsCurrBuild();
1839                 mStatsCurrBuild.clear();
1840                 mergeConnectionStats(statsCurr, mStatsCurrBuild);
1841             }
1842             if (ns.hasStatsPrevBuild()) {
1843                 ConnectionStats statsPrev = ns.getStatsPrevBuild();
1844                 mStatsPrevBuild.clear();
1845                 mergeConnectionStats(statsPrev, mStatsPrevBuild);
1846             }
1847             if (ns.getFrequenciesList().size() > 0) {
1848                 // This merge assumes that whatever data is in memory is more recent that what's
1849                 // in store
1850                 List<Integer> mergedFrequencyList = mFrequencyList.getEntries();
1851                 mergedFrequencyList.addAll(ns.getFrequenciesList());
1852                 mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID);
1853                 for (int i = mergedFrequencyList.size() - 1; i >= 0; i--) {
1854                     mFrequencyList.add(mergedFrequencyList.get(i));
1855                 }
1856             }
1857             if (ns.hasBandwidthStatsAll()) {
1858                 mergeBandwidthStatsAll(ns.getBandwidthStatsAll(),
1859                         mBandwidthStatsValue, mBandwidthStatsCount);
1860             }
1861             return this;
1862         }
1863 
mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target)1864         private void mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target) {
1865             if (source.hasNumConnectionAttempt()) {
1866                 target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionAttempt());
1867             }
1868             if (source.hasNumConnectionFailure()) {
1869                 target.accumulate(CNT_CONNECTION_FAILURE, source.getNumConnectionFailure());
1870             }
1871             if (source.hasConnectionDurationSec()) {
1872                 target.accumulate(CNT_CONNECTION_DURATION_SEC, source.getConnectionDurationSec());
1873             }
1874             if (source.hasNumDisconnectionNonlocal()) {
1875                 target.accumulate(CNT_DISCONNECTION_NONLOCAL, source.getNumDisconnectionNonlocal());
1876             }
1877             if (source.hasNumDisconnection()) {
1878                 target.accumulate(CNT_DISCONNECTION, source.getNumDisconnection());
1879             }
1880             if (source.hasNumShortConnectionNonlocal()) {
1881                 target.accumulate(CNT_SHORT_CONNECTION_NONLOCAL,
1882                         source.getNumShortConnectionNonlocal());
1883             }
1884             if (source.hasNumAssociationRejection()) {
1885                 target.accumulate(CNT_ASSOCIATION_REJECTION, source.getNumAssociationRejection());
1886             }
1887             if (source.hasNumAssociationTimeout()) {
1888                 target.accumulate(CNT_ASSOCIATION_TIMEOUT, source.getNumAssociationTimeout());
1889             }
1890             if (source.hasNumAuthenticationFailure()) {
1891                 target.accumulate(CNT_AUTHENTICATION_FAILURE, source.getNumAuthenticationFailure());
1892             }
1893             if (source.hasNumDisconnectionNonlocalConnecting()) {
1894                 target.accumulate(CNT_DISCONNECTION_NONLOCAL_CONNECTING,
1895                         source.getNumDisconnectionNonlocalConnecting());
1896             }
1897         }
1898     }
1899 
1900     // Codes for various connection related counts
1901     public static final int CNT_INVALID = -1;
1902     public static final int CNT_CONNECTION_ATTEMPT = 0;
1903     public static final int CNT_CONNECTION_FAILURE = 1;
1904     public static final int CNT_CONNECTION_DURATION_SEC = 2;
1905     public static final int CNT_ASSOCIATION_REJECTION = 3;
1906     public static final int CNT_ASSOCIATION_TIMEOUT = 4;
1907     public static final int CNT_AUTHENTICATION_FAILURE = 5;
1908     public static final int CNT_SHORT_CONNECTION_NONLOCAL = 6;
1909     public static final int CNT_DISCONNECTION_NONLOCAL = 7;
1910     public static final int CNT_DISCONNECTION = 8;
1911     public static final int CNT_CONSECUTIVE_CONNECTION_FAILURE = 9;
1912     public static final int CNT_DISCONNECTION_NONLOCAL_CONNECTING = 10;
1913     public static final int CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE = 11;
1914     // Constant being used to keep track of how many counter there are.
1915     public static final int NUMBER_CONNECTION_CNT_CODE = 12;
1916     private static final String[] CONNECTION_CNT_NAME = {
1917         " ConnectAttempt: ",
1918         " ConnectFailure: ",
1919         " ConnectDurSec: ",
1920         " AssocRej: ",
1921         " AssocTimeout: ",
1922         " AuthFailure: ",
1923         " ShortDiscNonlocal: ",
1924         " DisconnectNonlocal: ",
1925         " Disconnect: ",
1926         " ConsecutiveConnectFailure: ",
1927         " ConnectFailureDiscon: ",
1928         " ConsecutiveWrongPassword: "
1929     };
1930 
1931     @IntDef(prefix = { "CNT_" }, value = {
1932         CNT_CONNECTION_ATTEMPT,
1933         CNT_CONNECTION_FAILURE,
1934         CNT_CONNECTION_DURATION_SEC,
1935         CNT_ASSOCIATION_REJECTION,
1936         CNT_ASSOCIATION_TIMEOUT,
1937         CNT_AUTHENTICATION_FAILURE,
1938         CNT_SHORT_CONNECTION_NONLOCAL,
1939         CNT_DISCONNECTION_NONLOCAL,
1940         CNT_DISCONNECTION,
1941         CNT_CONSECUTIVE_CONNECTION_FAILURE,
1942         CNT_DISCONNECTION_NONLOCAL_CONNECTING,
1943         CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE
1944     })
1945     @Retention(RetentionPolicy.SOURCE)
1946     public @interface ConnectionCountCode {}
1947 
1948     /**
1949      * A class maintaining the connection related statistics of a Wifi network.
1950      */
1951     public static class NetworkConnectionStats {
1952         private final int[] mCount = new int[NUMBER_CONNECTION_CNT_CODE];
1953         private int mRecentCountCode = CNT_INVALID;
1954         /**
1955          * Copy all values
1956          * @param src is the source of copy
1957          */
copy(NetworkConnectionStats src)1958         public void copy(NetworkConnectionStats src) {
1959             for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
1960                 mCount[i] = src.getCount(i);
1961             }
1962             mRecentCountCode = src.mRecentCountCode;
1963         }
1964 
1965         /**
1966          * Clear all counters
1967          */
clear()1968         public void clear() {
1969             for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
1970                 mCount[i] = 0;
1971             }
1972             mRecentCountCode = CNT_INVALID;
1973         }
1974 
1975         /**
1976          * Get counter value
1977          * @param countCode is the selected counter
1978          * @return the value of selected counter
1979          */
getCount(@onnectionCountCode int countCode)1980         public int getCount(@ConnectionCountCode int countCode) {
1981             return mCount[countCode];
1982         }
1983 
1984         /**
1985          * Clear counter value
1986          * @param countCode is the selected counter to be cleared
1987          */
clearCount(@onnectionCountCode int countCode)1988         public void clearCount(@ConnectionCountCode int countCode) {
1989             mCount[countCode] = 0;
1990         }
1991 
1992         /**
1993          * Increment count value by 1
1994          * @param countCode is the selected counter
1995          */
incrementCount(@onnectionCountCode int countCode)1996         public void incrementCount(@ConnectionCountCode int countCode) {
1997             mCount[countCode]++;
1998             mRecentCountCode = countCode;
1999         }
2000 
2001         /**
2002          * Got the recent incremented count code
2003          */
getRecentCountCode()2004         public int getRecentCountCode() {
2005             return mRecentCountCode;
2006         }
2007 
2008         /**
2009          * Decrement count value by 1
2010          * @param countCode is the selected counter
2011          */
decrementCount(@onnectionCountCode int countCode)2012         public void decrementCount(@ConnectionCountCode int countCode) {
2013             mCount[countCode]--;
2014         }
2015 
2016         /**
2017          * Add and accumulate the selected counter
2018          * @param countCode is the selected counter
2019          * @param cnt is the value to be added to the counter
2020          */
accumulate(@onnectionCountCode int countCode, int cnt)2021         public void accumulate(@ConnectionCountCode int countCode, int cnt) {
2022             mCount[countCode] += cnt;
2023         }
2024 
2025         /**
2026          * Accumulate daily stats to historical data
2027          * @param recentStats are the raw daily counts
2028          */
accumulateAll(NetworkConnectionStats recentStats)2029         public void accumulateAll(NetworkConnectionStats recentStats) {
2030             // 32-bit counter in second can support connection duration up to 68 years.
2031             // Similarly 32-bit counter can support up to continuous connection attempt
2032             // up to 68 years with one attempt per second.
2033             for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
2034                 mCount[i] += recentStats.getCount(i);
2035             }
2036         }
2037 
2038         @Override
toString()2039         public String toString() {
2040             StringBuilder sb = new StringBuilder();
2041             for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
2042                 sb.append(CONNECTION_CNT_NAME[i]);
2043                 sb.append(mCount[i]);
2044             }
2045             return sb.toString();
2046         }
2047     }
2048 
2049     /**
2050      * A base class dealing with common operations of MemoryStore.
2051      */
2052     public static class MemoryStoreAccessBase {
2053         private final String mL2Key;
2054         private final long mHash;
2055         private static final String TAG = "WifiMemoryStoreAccessBase";
2056         private final AtomicReference<byte[]> mPendingReadFromStore = new AtomicReference<>();
MemoryStoreAccessBase(long hash)2057         MemoryStoreAccessBase(long hash) {
2058             mHash = hash;
2059             mL2Key = l2KeyFromLong();
2060         }
getL2Key()2061         String getL2Key() {
2062             return mL2Key;
2063         }
2064 
l2KeyFromLong()2065         private String l2KeyFromLong() {
2066             return "W" + Long.toHexString(mHash);
2067         }
2068 
2069         /**
2070          * Callback function when MemoryStore read is done
2071          * @param serialized is the readback value
2072          */
readBackListener(byte[] serialized)2073         void readBackListener(byte[] serialized) {
2074             if (serialized == null) return;
2075             byte[] old = mPendingReadFromStore.getAndSet(serialized);
2076             if (old != null) {
2077                 Log.e(TAG, "More answers than we expected!");
2078             }
2079         }
2080 
2081         /**
2082          * Handles (when convenient) the arrival of previously stored data.
2083          *
2084          * The response from IpMemoryStore arrives on a different thread, so we
2085          * defer handling it until here, when we're on our favorite thread and
2086          * in a good position to deal with it. We may have already collected some
2087          * data before now, so we need to be prepared to merge the new and old together.
2088          */
finishPendingReadBytes()2089         byte[] finishPendingReadBytes() {
2090             return mPendingReadFromStore.getAndSet(null);
2091         }
2092 
idFromLong()2093         int idFromLong() {
2094             return (int) mHash & 0x7fffffff;
2095         }
2096     }
2097 
logd(String string)2098     private void logd(String string) {
2099         if (mVerboseLoggingEnabled) {
2100             Log.d(TAG, string, null);
2101         }
2102     }
2103 
logv(String string)2104     private void logv(String string) {
2105         if (mVerboseLoggingEnabled) {
2106             Log.v(TAG, string, null);
2107         }
2108         mLocalLog.log(string);
2109     }
2110 
2111     // Returned by lookupBssid when the BSSID is not available,
2112     // for instance when we are not associated.
2113     private final PerBssid mPlaceholderPerBssid;
2114 
2115     private final Map<MacAddress, PerBssid> mApForBssid = new ArrayMap<>();
2116     private int mApForBssidTargetSize = TARGET_IN_MEMORY_ENTRIES;
2117     private int mApForBssidReferenced = 0;
2118 
2119     // TODO should be private, but WifiCandidates needs it
lookupBssid(String ssid, String bssid)2120     @NonNull PerBssid lookupBssid(String ssid, String bssid) {
2121         MacAddress mac;
2122         if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) || bssid == null) {
2123             return mPlaceholderPerBssid;
2124         }
2125         try {
2126             mac = MacAddress.fromString(bssid);
2127         } catch (IllegalArgumentException e) {
2128             return mPlaceholderPerBssid;
2129         }
2130         if (mac.equals(mPlaceholderPerBssid.bssid)) {
2131             return mPlaceholderPerBssid;
2132         }
2133         PerBssid ans = mApForBssid.get(mac);
2134         if (ans == null || !ans.ssid.equals(ssid)) {
2135             ans = new PerBssid(ssid, mac);
2136             PerBssid old = mApForBssid.put(mac, ans);
2137             if (old != null) {
2138                 Log.i(TAG, "Discarding stats for score card (ssid changed) ID: " + old.id);
2139                 if (old.referenced) mApForBssidReferenced--;
2140             }
2141             requestReadBssid(ans);
2142         }
2143         if (!ans.referenced) {
2144             ans.referenced = true;
2145             mApForBssidReferenced++;
2146             clean();
2147         }
2148         return ans;
2149     }
2150 
requestReadBssid(final PerBssid perBssid)2151     private void requestReadBssid(final PerBssid perBssid) {
2152         if (mMemoryStore != null) {
2153             mMemoryStore.read(perBssid.getL2Key(), PER_BSSID_DATA_NAME,
2154                     (value) -> perBssid.readBackListener(value));
2155         }
2156     }
2157 
requestReadForAllChanged()2158     private void requestReadForAllChanged() {
2159         for (PerBssid perBssid : mApForBssid.values()) {
2160             if (perBssid.changed) {
2161                 requestReadBssid(perBssid);
2162             }
2163         }
2164     }
2165 
2166     // Returned by lookupNetwork when the network is not available,
2167     // for instance when we are not associated.
2168     private final PerNetwork mPlaceholderPerNetwork;
2169     private final Map<String, PerNetwork> mApForNetwork = new ArrayMap<>();
lookupNetwork(String ssid)2170     @NonNull PerNetwork lookupNetwork(String ssid) {
2171         if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) {
2172             return mPlaceholderPerNetwork;
2173         }
2174 
2175         PerNetwork ans = mApForNetwork.get(ssid);
2176         if (ans == null) {
2177             ans = new PerNetwork(ssid);
2178             mApForNetwork.put(ssid, ans);
2179             requestReadNetwork(ans);
2180         }
2181         return ans;
2182     }
2183 
2184     /**
2185      * Remove network from cache and memory store
2186      * @param ssid is the network SSID
2187      */
removeNetwork(String ssid)2188     public void removeNetwork(String ssid) {
2189         if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) {
2190             return;
2191         }
2192         mApForNetwork.remove(ssid);
2193         mApForBssid.entrySet().removeIf(entry -> ssid.equals(entry.getValue().ssid));
2194         if (mMemoryStore == null) return;
2195         mMemoryStore.removeCluster(groupHintFromSsid(ssid));
2196     }
2197 
requestReadNetwork(final PerNetwork perNetwork)2198     void requestReadNetwork(final PerNetwork perNetwork) {
2199         if (mMemoryStore != null) {
2200             mMemoryStore.read(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME,
2201                     (value) -> perNetwork.readBackListener(value));
2202         }
2203     }
2204 
2205     /**
2206      * Issues write requests for all changed entries.
2207      *
2208      * This should be called from time to time to save the state to persistent
2209      * storage. Since we always check internal state first, this does not need
2210      * to be called very often, but it should be called before shutdown.
2211      *
2212      * @returns number of writes issued.
2213      */
doWrites()2214     public int doWrites() {
2215         return doWritesBssid() + doWritesNetwork();
2216     }
2217 
doWritesBssid()2218     private int doWritesBssid() {
2219         if (mMemoryStore == null) return 0;
2220         int count = 0;
2221         int bytes = 0;
2222         for (PerBssid perBssid : mApForBssid.values()) {
2223             if (perBssid.changed) {
2224                 perBssid.finishPendingRead();
2225                 byte[] serialized = perBssid.toAccessPoint(/* No BSSID */ true).toByteArray();
2226                 mMemoryStore.setCluster(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid));
2227                 mMemoryStore.write(perBssid.getL2Key(), PER_BSSID_DATA_NAME, serialized);
2228 
2229                 perBssid.changed = false;
2230                 count++;
2231                 bytes += serialized.length;
2232             }
2233         }
2234         if (mVerboseLoggingEnabled && count > 0) {
2235             Log.v(TAG, "Write count: " + count + ", bytes: " + bytes);
2236         }
2237         return count;
2238     }
2239 
doWritesNetwork()2240     private int doWritesNetwork() {
2241         if (mMemoryStore == null) return 0;
2242         int count = 0;
2243         int bytes = 0;
2244         for (PerNetwork perNetwork : mApForNetwork.values()) {
2245             if (perNetwork.changed) {
2246                 perNetwork.finishPendingRead();
2247                 byte[] serialized = perNetwork.toNetworkStats().toByteArray();
2248                 mMemoryStore.setCluster(perNetwork.getL2Key(), groupHintFromSsid(perNetwork.ssid));
2249                 mMemoryStore.write(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, serialized);
2250                 perNetwork.changed = false;
2251                 count++;
2252                 bytes += serialized.length;
2253             }
2254         }
2255         if (mVerboseLoggingEnabled && count > 0) {
2256             Log.v(TAG, "Write count: " + count + ", bytes: " + bytes);
2257         }
2258         return count;
2259     }
2260 
2261     /**
2262      * Evicts older entries from memory.
2263      *
2264      * This uses an approximate least-recently-used method. When the number of
2265      * referenced entries exceeds the target value, any items that have not been
2266      * referenced since the last round are evicted, and the remaining entries
2267      * are marked as unreferenced. The total count varies between the target
2268      * value and twice the target value.
2269      */
clean()2270     private void clean() {
2271         if (mMemoryStore == null) return;
2272         if (mApForBssidReferenced >= mApForBssidTargetSize) {
2273             doWritesBssid(); // Do not want to evict changed items
2274             // Evict the unreferenced ones, and clear all the referenced bits for the next round.
2275             Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator();
2276             while (it.hasNext()) {
2277                 PerBssid perBssid = it.next().getValue();
2278                 if (perBssid.referenced) {
2279                     perBssid.referenced = false;
2280                 } else {
2281                     it.remove();
2282                     if (mVerboseLoggingEnabled) Log.v(TAG, "Evict " + perBssid.id);
2283                 }
2284             }
2285             mApForBssidReferenced = 0;
2286         }
2287     }
2288 
2289     /**
2290      * Compute a hash value with the given SSID and MAC address
2291      * @param ssid is the network SSID
2292      * @param mac is the network MAC address
2293      * @param l2KeySeed is the seed for hash generation
2294      * @return
2295      */
computeHashLong(String ssid, MacAddress mac, String l2KeySeed)2296     public static long computeHashLong(String ssid, MacAddress mac, String l2KeySeed) {
2297         final ArrayList<Byte> decodedSsid;
2298         try {
2299             decodedSsid = NativeUtil.decodeSsid(ssid);
2300         } catch (IllegalArgumentException e) {
2301             Log.e(TAG, "NativeUtil.decodeSsid failed: malformed string: " + ssid);
2302             return 0;
2303         }
2304         byte[][] parts = {
2305                 // Our seed keeps the L2Keys specific to this device
2306                 l2KeySeed.getBytes(),
2307                 // ssid is either quoted utf8 or hex-encoded bytes; turn it into plain bytes.
2308                 NativeUtil.byteArrayFromArrayList(decodedSsid),
2309                 // And the BSSID
2310                 mac.toByteArray()
2311         };
2312         // Assemble the parts into one, with single-byte lengths before each.
2313         int n = 0;
2314         for (int i = 0; i < parts.length; i++) {
2315             n += 1 + parts[i].length;
2316         }
2317         byte[] mashed = new byte[n];
2318         int p = 0;
2319         for (int i = 0; i < parts.length; i++) {
2320             byte[] part = parts[i];
2321             mashed[p++] = (byte) part.length;
2322             for (int j = 0; j < part.length; j++) {
2323                 mashed[p++] = part[j];
2324             }
2325         }
2326         // Finally, turn that into a long
2327         MessageDigest md;
2328         try {
2329             md = MessageDigest.getInstance("SHA-256");
2330         } catch (NoSuchAlgorithmException e) {
2331             Log.e(TAG, "SHA-256 not supported.");
2332             return 0;
2333         }
2334         ByteBuffer buffer = ByteBuffer.wrap(md.digest(mashed));
2335         return buffer.getLong();
2336     }
2337 
groupHintFromLong(long hash)2338     private static String groupHintFromLong(long hash) {
2339         return "G" + Long.toHexString(hash);
2340     }
2341 
2342     @VisibleForTesting
fetchByBssid(MacAddress mac)2343     PerBssid fetchByBssid(MacAddress mac) {
2344         return mApForBssid.get(mac);
2345     }
2346 
2347     @VisibleForTesting
fetchByNetwork(String ssid)2348     PerNetwork fetchByNetwork(String ssid) {
2349         return mApForNetwork.get(ssid);
2350     }
2351 
2352     @VisibleForTesting
perBssidFromAccessPoint(String ssid, AccessPoint ap)2353     PerBssid perBssidFromAccessPoint(String ssid, AccessPoint ap) {
2354         MacAddress bssid = MacAddress.fromBytes(ap.getBssid().toByteArray());
2355         return new PerBssid(ssid, bssid).merge(ap);
2356     }
2357 
2358     @VisibleForTesting
perNetworkFromNetworkStats(String ssid, NetworkStats ns)2359     PerNetwork perNetworkFromNetworkStats(String ssid, NetworkStats ns) {
2360         return new PerNetwork(ssid).mergeNetworkStatsFromMemory(ns);
2361     }
2362 
2363     final class PerSignal {
2364         public final Event event;
2365         public final int frequency;
2366         public final PerUnivariateStatistic rssi;
2367         public final PerUnivariateStatistic linkspeed;
2368         @Nullable public final PerUnivariateStatistic elapsedMs;
PerSignal(Event event, int frequency)2369         PerSignal(Event event, int frequency) {
2370             this.event = event;
2371             this.frequency = frequency;
2372             switch (event) {
2373                 case SIGNAL_POLL:
2374                 case IP_CONFIGURATION_SUCCESS:
2375                 case IP_REACHABILITY_LOST:
2376                     this.rssi = new PerUnivariateStatistic(RSSI_BUCKETS);
2377                     break;
2378                 default:
2379                     this.rssi = new PerUnivariateStatistic();
2380                     break;
2381             }
2382             this.linkspeed = new PerUnivariateStatistic();
2383             switch (event) {
2384                 case FIRST_POLL_AFTER_CONNECTION:
2385                 case IP_CONFIGURATION_SUCCESS:
2386                 case VALIDATION_SUCCESS:
2387                 case CONNECTION_FAILURE:
2388                 case DISCONNECTION:
2389                 case WIFI_DISABLED:
2390                 case ROAM_FAILURE:
2391                     this.elapsedMs = new PerUnivariateStatistic();
2392                     break;
2393                 default:
2394                     this.elapsedMs = null;
2395                     break;
2396             }
2397         }
merge(Signal signal)2398         PerSignal merge(Signal signal) {
2399             Preconditions.checkArgument(event == signal.getEvent());
2400             Preconditions.checkArgument(frequency == signal.getFrequency());
2401             rssi.merge(signal.getRssi());
2402             linkspeed.merge(signal.getLinkspeed());
2403             if (elapsedMs != null && signal.hasElapsedMs()) {
2404                 elapsedMs.merge(signal.getElapsedMs());
2405             }
2406             return this;
2407         }
toSignal()2408         Signal toSignal() {
2409             Signal.Builder builder = Signal.newBuilder();
2410             builder.setEvent(event)
2411                     .setFrequency(frequency)
2412                     .setRssi(rssi.toUnivariateStatistic())
2413                     .setLinkspeed(linkspeed.toUnivariateStatistic());
2414             if (elapsedMs != null) {
2415                 builder.setElapsedMs(elapsedMs.toUnivariateStatistic());
2416             }
2417             if (rssi.intHistogram != null
2418                     && rssi.intHistogram.numNonEmptyBuckets() > 0) {
2419                 logd("Histogram " + event + " RSSI" + rssi.intHistogram);
2420             }
2421             return builder.build();
2422         }
2423     }
2424 
2425     final class PerUnivariateStatistic {
2426         public long count = 0;
2427         public double sum = 0.0;
2428         public double sumOfSquares = 0.0;
2429         public double minValue = Double.POSITIVE_INFINITY;
2430         public double maxValue = Double.NEGATIVE_INFINITY;
2431         public double historicalMean = 0.0;
2432         public double historicalVariance = Double.POSITIVE_INFINITY;
2433         public IntHistogram intHistogram = null;
PerUnivariateStatistic()2434         PerUnivariateStatistic() {}
PerUnivariateStatistic(int[] bucketBoundaries)2435         PerUnivariateStatistic(int[] bucketBoundaries) {
2436             intHistogram = new IntHistogram(bucketBoundaries);
2437         }
update(double value)2438         void update(double value) {
2439             count++;
2440             sum += value;
2441             sumOfSquares += value * value;
2442             minValue = Math.min(minValue, value);
2443             maxValue = Math.max(maxValue, value);
2444             if (intHistogram != null) {
2445                 intHistogram.add(Math.round((float) value), 1);
2446             }
2447         }
age()2448         void age() {
2449             //TODO  Fold the current stats into the historical stats
2450         }
merge(UnivariateStatistic stats)2451         void merge(UnivariateStatistic stats) {
2452             if (stats.hasCount()) {
2453                 count += stats.getCount();
2454                 sum += stats.getSum();
2455                 sumOfSquares += stats.getSumOfSquares();
2456             }
2457             if (stats.hasMinValue()) {
2458                 minValue = Math.min(minValue, stats.getMinValue());
2459             }
2460             if (stats.hasMaxValue()) {
2461                 maxValue = Math.max(maxValue, stats.getMaxValue());
2462             }
2463             if (stats.hasHistoricalVariance()) {
2464                 if (historicalVariance < Double.POSITIVE_INFINITY) {
2465                     // Combine the estimates; c.f.
2466                     // Maybeck, Stochasic Models, Estimation, and Control, Vol. 1
2467                     // equations (1-3) and (1-4)
2468                     double numer1 = stats.getHistoricalVariance();
2469                     double numer2 = historicalVariance;
2470                     double denom = numer1 + numer2;
2471                     historicalMean = (numer1 * historicalMean
2472                                     + numer2 * stats.getHistoricalMean())
2473                                     / denom;
2474                     historicalVariance = numer1 * numer2 / denom;
2475                 } else {
2476                     historicalMean = stats.getHistoricalMean();
2477                     historicalVariance = stats.getHistoricalVariance();
2478                 }
2479             }
2480             if (intHistogram != null) {
2481                 for (HistogramBucket bucket : stats.getBucketsList()) {
2482                     long low = bucket.getLow();
2483                     long count = bucket.getNumber();
2484                     if (low != (int) low || count != (int) count || count < 0) {
2485                         Log.e(TAG, "Found corrupted histogram! Clearing.");
2486                         intHistogram.clear();
2487                         break;
2488                     }
2489                     intHistogram.add((int) low, (int) count);
2490                 }
2491             }
2492         }
toUnivariateStatistic()2493         UnivariateStatistic toUnivariateStatistic() {
2494             UnivariateStatistic.Builder builder = UnivariateStatistic.newBuilder();
2495             if (count != 0) {
2496                 builder.setCount(count)
2497                         .setSum(sum)
2498                         .setSumOfSquares(sumOfSquares)
2499                         .setMinValue(minValue)
2500                         .setMaxValue(maxValue);
2501             }
2502             if (historicalVariance < Double.POSITIVE_INFINITY) {
2503                 builder.setHistoricalMean(historicalMean)
2504                         .setHistoricalVariance(historicalVariance);
2505             }
2506             if (mPersistentHistograms
2507                     && intHistogram != null && intHistogram.numNonEmptyBuckets() > 0) {
2508                 for (IntHistogram.Bucket b : intHistogram) {
2509                     if (b.count == 0) continue;
2510                     builder.addBuckets(
2511                             HistogramBucket.newBuilder().setLow(b.start).setNumber(b.count));
2512                 }
2513             }
2514             return builder.build();
2515         }
2516     }
2517 
2518     /**
2519      * Returns the current scorecard in the form of a protobuf com_android_server_wifi.NetworkList
2520      *
2521      * Synchronization is the caller's responsibility.
2522      *
2523      * @param obfuscate - if true, ssids and bssids are omitted (short id only)
2524      */
getNetworkListByteArray(boolean obfuscate)2525     public byte[] getNetworkListByteArray(boolean obfuscate) {
2526         // These are really grouped by ssid, ignoring the security type.
2527         Map<String, Network.Builder> networks = new ArrayMap<>();
2528         for (PerBssid perBssid: mApForBssid.values()) {
2529             String key = perBssid.ssid;
2530             Network.Builder network = networks.get(key);
2531             if (network == null) {
2532                 network = Network.newBuilder();
2533                 networks.put(key, network);
2534                 if (!obfuscate) {
2535                     network.setSsid(perBssid.ssid);
2536                 }
2537             }
2538             if (perBssid.mNetworkAgentId >= network.getNetworkAgentId()) {
2539                 network.setNetworkAgentId(perBssid.mNetworkAgentId);
2540             }
2541             if (perBssid.mNetworkConfigId >= network.getNetworkConfigId()) {
2542                 network.setNetworkConfigId(perBssid.mNetworkConfigId);
2543             }
2544             network.addAccessPoints(perBssid.toAccessPoint(obfuscate));
2545         }
2546         for (PerNetwork perNetwork: mApForNetwork.values()) {
2547             String key = perNetwork.ssid;
2548             Network.Builder network = networks.get(key);
2549             if (network != null) {
2550                 network.setNetworkStats(perNetwork.toNetworkStats());
2551             }
2552         }
2553         NetworkList.Builder builder = NetworkList.newBuilder();
2554         for (Network.Builder network: networks.values()) {
2555             builder.addNetworks(network);
2556         }
2557         return builder.build().toByteArray();
2558     }
2559 
2560     /**
2561      * Returns the current scorecard as a base64-encoded protobuf
2562      *
2563      * Synchronization is the caller's responsibility.
2564      *
2565      * @param obfuscate - if true, bssids are omitted (short id only)
2566      */
getNetworkListBase64(boolean obfuscate)2567     public String getNetworkListBase64(boolean obfuscate) {
2568         byte[] raw = getNetworkListByteArray(obfuscate);
2569         return Base64.encodeToString(raw, Base64.DEFAULT);
2570     }
2571 
2572     /**
2573      * Clears the internal state.
2574      *
2575      * This is called in response to a factoryReset call from Settings.
2576      * The memory store will be called after we are called, to wipe the stable
2577      * storage as well. Since we will have just removed all of our networks,
2578      * it is very unlikely that we're connected, or will connect immediately.
2579      * Any in-flight reads will land in the objects we are dropping here, and
2580      * the memory store should drop the in-flight writes. Ideally we would
2581      * avoid issuing reads until we were sure that the memory store had
2582      * received the factoryReset.
2583      */
clear()2584     public void clear() {
2585         mApForBssid.clear();
2586         mApForNetwork.clear();
2587         resetAllConnectionStatesInternal();
2588     }
2589 
2590     /**
2591      * build bandwidth estimator stats proto and then clear all related counters
2592      */
dumpBandwidthEstimatorStats()2593     public BandwidthEstimatorStats dumpBandwidthEstimatorStats() {
2594         BandwidthEstimatorStats stats = new BandwidthEstimatorStats();
2595         stats.stats2G = dumpBandwdithStatsPerBand(0);
2596         stats.statsAbove2G = dumpBandwdithStatsPerBand(1);
2597         return stats;
2598     }
2599 
dumpBandwdithStatsPerBand(int bandIdx)2600     private BandwidthEstimatorStats.PerBand dumpBandwdithStatsPerBand(int bandIdx) {
2601         BandwidthEstimatorStats.PerBand stats = new BandwidthEstimatorStats.PerBand();
2602         stats.tx = dumpBandwidthStatsPerLink(bandIdx, LINK_TX);
2603         stats.rx = dumpBandwidthStatsPerLink(bandIdx, LINK_RX);
2604         return stats;
2605     }
2606 
dumpBandwidthStatsPerLink( int bandIdx, int linkIdx)2607     private BandwidthEstimatorStats.PerLink dumpBandwidthStatsPerLink(
2608             int bandIdx, int linkIdx) {
2609         BandwidthEstimatorStats.PerLink stats = new BandwidthEstimatorStats.PerLink();
2610         List<BandwidthEstimatorStats.PerLevel> levels = new ArrayList<>();
2611         for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) {
2612             BandwidthEstimatorStats.PerLevel currStats =
2613                     dumpBandwidthStatsPerLevel(bandIdx, linkIdx, level);
2614             if (currStats != null) {
2615                 levels.add(currStats);
2616             }
2617         }
2618         stats.level = levels.toArray(new BandwidthEstimatorStats.PerLevel[0]);
2619         return stats;
2620     }
2621 
dumpBandwidthStatsPerLevel( int bandIdx, int linkIdx, int level)2622     private BandwidthEstimatorStats.PerLevel dumpBandwidthStatsPerLevel(
2623             int bandIdx, int linkIdx, int level) {
2624         int count = mBwEstCount[bandIdx][linkIdx][level];
2625         if (count <= 0) {
2626             return null;
2627         }
2628 
2629         BandwidthEstimatorStats.PerLevel stats = new BandwidthEstimatorStats.PerLevel();
2630         stats.signalLevel = level;
2631         stats.count = count;
2632         stats.avgBandwidthKbps = calculateAvg(mBwEstValue[bandIdx][linkIdx][level], count);
2633         stats.l2ErrorPercent = calculateAvg(
2634                 mL2ErrorAccPercent[bandIdx][linkIdx][level], count);
2635         stats.bandwidthEstErrorPercent = calculateAvg(
2636                 mBwEstErrorAccPercent[bandIdx][linkIdx][level], count);
2637 
2638         // reset counters for next run
2639         mBwEstCount[bandIdx][linkIdx][level] = 0;
2640         mBwEstValue[bandIdx][linkIdx][level] = 0;
2641         mL2ErrorAccPercent[bandIdx][linkIdx][level] = 0;
2642         mBwEstErrorAccPercent[bandIdx][linkIdx][level] = 0;
2643         return stats;
2644     }
2645 
calculateAvg(long acc, int count)2646     private int calculateAvg(long acc, int count) {
2647         return (count > 0) ? (int) (acc / count) : 0;
2648     }
2649 
2650     /**
2651      * Dump the internal state and local logs
2652      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)2653     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2654         pw.println("Dump of WifiScoreCard");
2655         pw.println("current SSID(s):" + mIfaceToInfoMap.entrySet().stream()
2656                 .map(entry ->
2657                         "{iface=" + entry.getKey() + ",ssid=" + entry.getValue().ssidCurr + "}")
2658                 .collect(Collectors.joining(",")));
2659         try {
2660             mLocalLog.dump(fd, pw, args);
2661         } catch (Exception e) {
2662             e.printStackTrace();
2663         }
2664 
2665         pw.println(" BW Estimation Stats");
2666         for (int i = 0; i < 2; i++) {
2667             pw.println((i == 0 ? "2G" : "5G"));
2668             for (int j = 0; j < NUM_LINK_DIRECTION; j++) {
2669                 pw.println((j == 0 ? " Tx" : " Rx"));
2670                 pw.println(" Count");
2671                 printValues(mBwEstCount[i][j], pw);
2672                 pw.println(" AvgKbps");
2673                 printAvgStats(mBwEstValue[i][j], mBwEstCount[i][j], pw);
2674                 pw.println(" BwEst error");
2675                 printAvgStats(mBwEstErrorAccPercent[i][j], mBwEstCount[i][j], pw);
2676                 pw.println(" L2 error");
2677                 printAvgStats(mL2ErrorAccPercent[i][j], mBwEstCount[i][j], pw);
2678             }
2679         }
2680         pw.println();
2681     }
2682 
printValues(int[] values, PrintWriter pw)2683     private void printValues(int[] values, PrintWriter pw) {
2684         StringBuilder sb = new StringBuilder();
2685         for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
2686             sb.append(" " + values[k]);
2687         }
2688         pw.println(sb.toString());
2689     }
2690 
printAvgStats(long[] stats, int[] count, PrintWriter pw)2691     private void printAvgStats(long[] stats, int[] count, PrintWriter pw) {
2692         StringBuilder sb = new StringBuilder();
2693         for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) {
2694             sb.append(" " + calculateAvg(stats[k], count[k]));
2695         }
2696         pw.println(sb.toString());
2697     }
2698 }
2699