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