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