1 /*
2  * Copyright (C) 2016 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 android.net.Network;
20 import android.net.NetworkAgent;
21 import android.net.wifi.IScoreUpdateObserver;
22 import android.net.wifi.IWifiConnectedNetworkScorer;
23 import android.net.wifi.WifiInfo;
24 import android.net.wifi.nl80211.WifiNl80211Manager;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.io.FileDescriptor;
30 import java.io.PrintWriter;
31 import java.text.SimpleDateFormat;
32 import java.util.Date;
33 import java.util.LinkedList;
34 import java.util.Locale;
35 
36 /**
37  * Class used to calculate scores for connected wifi networks and report it to the associated
38  * network agent.
39  */
40 public class WifiScoreReport {
41     private static final String TAG = "WifiScoreReport";
42 
43     private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 3600; // 3 hours on 3 second poll
44 
45     private boolean mVerboseLoggingEnabled = false;
46     private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016
47 
48     private static final long MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS = 9000;
49     private long mLastDownwardBreachTimeMillis = 0;
50 
51     private static final int WIFI_CONNECTED_NETWORK_SCORER_IDENTIFIER = 0;
52     private static final int INVALID_SESSION_ID = -1;
53     private static final long MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS = 29000;
54     private static final long DURATION_TO_BLOCKLIST_BSSID_AFTER_FIRST_EXITING_MILLIS = 30000;
55     private static final long INVALID_WALL_CLOCK_MILLIS = -1;
56 
57     // Cache of the last score
58     private int mScore = ConnectedScore.WIFI_MAX_SCORE;
59 
60     private final ScoringParams mScoringParams;
61     private final Clock mClock;
62     private int mSessionNumber = 0; // not to be confused with sessionid, this just counts resets
63     private String mInterfaceName;
64     private final BssidBlocklistMonitor mBssidBlocklistMonitor;
65     private long mLastScoreBreachLowTimeMillis = -1;
66 
67     ConnectedScore mAggressiveConnectedScore;
68     VelocityBasedConnectedScore mVelocityBasedConnectedScore;
69 
70     NetworkAgent mNetworkAgent;
71     WifiMetrics mWifiMetrics;
72     WifiInfo mWifiInfo;
73     WifiNative mWifiNative;
74     WifiThreadRunner mWifiThreadRunner;
75 
76     /**
77      * Callback proxy. See {@link android.net.wifi.WifiManager.ScoreUpdateObserver}.
78      */
79     private class ScoreUpdateObserverProxy extends IScoreUpdateObserver.Stub {
80         @Override
notifyScoreUpdate(int sessionId, int score)81         public void notifyScoreUpdate(int sessionId, int score) {
82             mWifiThreadRunner.post(() -> {
83                 if (mWifiConnectedNetworkScorerHolder == null
84                         || sessionId == INVALID_SESSION_ID
85                         || sessionId != getCurrentSessionId()) {
86                     Log.w(TAG, "Ignoring stale/invalid external score"
87                              + " sessionId=" + sessionId
88                              + " currentSessionId=" + getCurrentSessionId()
89                              + " score=" + score);
90                     return;
91                 }
92                 if (mNetworkAgent != null) {
93                     mNetworkAgent.sendNetworkScore(score);
94                 }
95 
96                 long millis = mClock.getWallClockMillis();
97                 if (score < ConnectedScore.WIFI_TRANSITION_SCORE) {
98                     if (mScore >= ConnectedScore.WIFI_TRANSITION_SCORE) {
99                         mLastScoreBreachLowTimeMillis = millis;
100                     }
101                 } else {
102                     mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
103                 }
104 
105                 mScore = score;
106                 updateWifiMetrics(millis, -1, mScore);
107             });
108         }
109 
110         @Override
triggerUpdateOfWifiUsabilityStats(int sessionId)111         public void triggerUpdateOfWifiUsabilityStats(int sessionId) {
112             mWifiThreadRunner.post(() -> {
113                 if (mWifiConnectedNetworkScorerHolder == null
114                         || sessionId == INVALID_SESSION_ID
115                         || sessionId != getCurrentSessionId()
116                         || mInterfaceName == null) {
117                     Log.w(TAG, "Ignoring triggerUpdateOfWifiUsabilityStats"
118                              + " sessionId=" + sessionId
119                              + " currentSessionId=" + getCurrentSessionId()
120                              + " interfaceName=" + mInterfaceName);
121                     return;
122                 }
123                 WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
124 
125                 // update mWifiInfo
126                 // TODO(b/153075963): Better coordinate this class and ClientModeImpl to remove
127                 // redundant codes below and in ClientModeImpl#fetchRssiLinkSpeedAndFrequencyNative.
128                 WifiNl80211Manager.SignalPollResult pollResult =
129                         mWifiNative.signalPoll(mInterfaceName);
130                 if (pollResult != null) {
131                     int newRssi = pollResult.currentRssiDbm;
132                     int newTxLinkSpeed = pollResult.txBitrateMbps;
133                     int newFrequency = pollResult.associationFrequencyMHz;
134                     int newRxLinkSpeed = pollResult.rxBitrateMbps;
135 
136                     if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
137                         if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
138                             Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
139                             newRssi -= 256;
140                         }
141                         mWifiInfo.setRssi(newRssi);
142                     } else {
143                         mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
144                     }
145                     /*
146                      * set Tx link speed only if it is valid
147                      */
148                     if (newTxLinkSpeed > 0) {
149                         mWifiInfo.setLinkSpeed(newTxLinkSpeed);
150                         mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
151                     }
152                     /*
153                      * set Rx link speed only if it is valid
154                      */
155                     if (newRxLinkSpeed > 0) {
156                         mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
157                     }
158                     if (newFrequency > 0) {
159                         mWifiInfo.setFrequency(newFrequency);
160                     }
161                 }
162 
163                 // TODO(b/153075963): This should not be plumbed through WifiMetrics
164                 mWifiMetrics.updateWifiUsabilityStatsEntries(mWifiInfo, stats);
165             });
166         }
167     }
168 
169     /**
170      * Container for storing info about external scorer and tracking its death.
171      */
172     private final class WifiConnectedNetworkScorerHolder implements IBinder.DeathRecipient {
173         private final IBinder mBinder;
174         private final IWifiConnectedNetworkScorer mScorer;
175         private int mSessionId = INVALID_SESSION_ID;
176 
WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer)177         WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer) {
178             mBinder = binder;
179             mScorer = scorer;
180         }
181 
182         /**
183          * Link WiFi connected scorer to death listener.
184          */
linkScorerToDeath()185         public boolean linkScorerToDeath() {
186             try {
187                 mBinder.linkToDeath(this, 0);
188             } catch (RemoteException e) {
189                 Log.e(TAG, "Unable to linkToDeath Wifi connected network scorer " + mScorer, e);
190                 return false;
191             }
192             return true;
193         }
194 
195         /**
196          * App hosting the binder has died.
197          */
198         @Override
binderDied()199         public void binderDied() {
200             mWifiThreadRunner.post(() -> revertToDefaultConnectedScorer());
201         }
202 
203         /**
204          * Unlink this object from binder death.
205          */
reset()206         public void reset() {
207             mBinder.unlinkToDeath(this, 0);
208         }
209 
210         /**
211          * Starts a new scoring session.
212          */
startSession(int sessionId)213         public void startSession(int sessionId) {
214             if (sessionId == INVALID_SESSION_ID) {
215                 throw new IllegalArgumentException();
216             }
217             if (mSessionId != INVALID_SESSION_ID) {
218                 // This is not expected to happen, log if it does
219                 Log.e(TAG, "Stopping session " + mSessionId + " before starting " + sessionId);
220                 stopSession();
221             }
222             // Bail now if the scorer has gone away
223             if (this != mWifiConnectedNetworkScorerHolder) {
224                 return;
225             }
226             mSessionId = sessionId;
227             try {
228                 mScorer.onStart(sessionId);
229             } catch (RemoteException e) {
230                 Log.e(TAG, "Unable to start Wifi connected network scorer " + this, e);
231                 revertToDefaultConnectedScorer();
232             }
233         }
stopSession()234         public void stopSession() {
235             final int sessionId = mSessionId;
236             if (sessionId == INVALID_SESSION_ID) return;
237             mSessionId = INVALID_SESSION_ID;
238             try {
239                 mScorer.onStop(sessionId);
240             } catch (RemoteException e) {
241                 Log.e(TAG, "Unable to stop Wifi connected network scorer " + this, e);
242                 revertToDefaultConnectedScorer();
243             }
244         }
245     }
246 
247     private final ScoreUpdateObserverProxy mScoreUpdateObserver =
248             new ScoreUpdateObserverProxy();
249 
250     private WifiConnectedNetworkScorerHolder mWifiConnectedNetworkScorerHolder;
251 
WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics, WifiInfo wifiInfo, WifiNative wifiNative, BssidBlocklistMonitor bssidBlocklistMonitor, WifiThreadRunner wifiThreadRunner)252     WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics,
253             WifiInfo wifiInfo, WifiNative wifiNative, BssidBlocklistMonitor bssidBlocklistMonitor,
254             WifiThreadRunner wifiThreadRunner) {
255         mScoringParams = scoringParams;
256         mClock = clock;
257         mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
258         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock);
259         mWifiMetrics = wifiMetrics;
260         mWifiInfo = wifiInfo;
261         mWifiNative = wifiNative;
262         mBssidBlocklistMonitor = bssidBlocklistMonitor;
263         mWifiThreadRunner = wifiThreadRunner;
264     }
265 
266     /**
267      * Reset the last calculated score.
268      */
reset()269     public void reset() {
270         mSessionNumber++;
271         mScore = ConnectedScore.WIFI_MAX_SCORE;
272         mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
273         mAggressiveConnectedScore.reset();
274         if (mVelocityBasedConnectedScore != null) {
275             mVelocityBasedConnectedScore.reset();
276         }
277         mLastDownwardBreachTimeMillis = 0;
278         mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
279         if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
280     }
281 
282     /**
283      * Enable/Disable verbose logging in score report generation.
284      */
enableVerboseLogging(boolean enable)285     public void enableVerboseLogging(boolean enable) {
286         mVerboseLoggingEnabled = enable;
287     }
288 
289     /**
290      * Calculate wifi network score based on updated link layer stats and send the score to
291      * the WifiNetworkAgent.
292      *
293      * If the score has changed from the previous value, update the WifiNetworkAgent.
294      *
295      * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
296      */
calculateAndReportScore()297     public void calculateAndReportScore() {
298         // Bypass AOSP scorer if Wifi connected network scorer is set
299         if (mWifiConnectedNetworkScorerHolder != null) {
300             return;
301         }
302 
303         if (mWifiInfo.getRssi() == mWifiInfo.INVALID_RSSI) {
304             Log.d(TAG, "Not reporting score because RSSI is invalid");
305             return;
306         }
307         int score;
308 
309         long millis = mClock.getWallClockMillis();
310         mVelocityBasedConnectedScore.updateUsingWifiInfo(mWifiInfo, millis);
311 
312         int s2 = mVelocityBasedConnectedScore.generateScore();
313         score = s2;
314 
315         if (mWifiInfo.getScore() > ConnectedScore.WIFI_TRANSITION_SCORE
316                 && score <= ConnectedScore.WIFI_TRANSITION_SCORE
317                 && mWifiInfo.getSuccessfulTxPacketsPerSecond()
318                         >= mScoringParams.getYippeeSkippyPacketsPerSecond()
319                 && mWifiInfo.getSuccessfulRxPacketsPerSecond()
320                         >= mScoringParams.getYippeeSkippyPacketsPerSecond()
321         ) {
322             score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
323         }
324 
325         if (mWifiInfo.getScore() > ConnectedScore.WIFI_TRANSITION_SCORE
326                 && score <= ConnectedScore.WIFI_TRANSITION_SCORE) {
327             // We don't want to trigger a downward breach unless the rssi is
328             // below the entry threshold.  There is noise in the measured rssi, and
329             // the kalman-filtered rssi is affected by the trend, so check them both.
330             // TODO(b/74613347) skip this if there are other indications to support the low score
331             int entry = mScoringParams.getEntryRssi(mWifiInfo.getFrequency());
332             if (mVelocityBasedConnectedScore.getFilteredRssi() >= entry
333                     || mWifiInfo.getRssi() >= entry) {
334                 // Stay a notch above the transition score to reduce ambiguity.
335                 score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
336             }
337         }
338 
339         if (mWifiInfo.getScore() >= ConnectedScore.WIFI_TRANSITION_SCORE
340                 && score < ConnectedScore.WIFI_TRANSITION_SCORE) {
341             mLastDownwardBreachTimeMillis = millis;
342         } else if (mWifiInfo.getScore() < ConnectedScore.WIFI_TRANSITION_SCORE
343                 && score >= ConnectedScore.WIFI_TRANSITION_SCORE) {
344             // Staying at below transition score for a certain period of time
345             // to prevent going back to wifi network again in a short time.
346             long elapsedMillis = millis - mLastDownwardBreachTimeMillis;
347             if (elapsedMillis < MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS) {
348                 score = mWifiInfo.getScore();
349             }
350         }
351         //sanitize boundaries
352         if (score > ConnectedScore.WIFI_MAX_SCORE) {
353             score = ConnectedScore.WIFI_MAX_SCORE;
354         }
355         if (score < 0) {
356             score = 0;
357         }
358 
359         //report score
360         if (score != mWifiInfo.getScore()) {
361             if (mNetworkAgent != null) {
362                 mNetworkAgent.sendNetworkScore(score);
363             }
364         }
365 
366         updateWifiMetrics(millis, s2, score);
367         mScore = score;
368     }
369 
getCurrentNetId()370     private int getCurrentNetId() {
371         int netId = 0;
372         if (mNetworkAgent != null) {
373             final Network network = mNetworkAgent.getNetwork();
374             if (network != null) {
375                 netId = network.getNetId();
376             }
377         }
378         return netId;
379     }
380 
getCurrentSessionId()381     private int getCurrentSessionId() {
382         return sessionIdFromNetId(getCurrentNetId());
383     }
384 
385     /**
386      * Encodes a network id into a scoring session id.
387      *
388      * We use a different numeric value for session id and the network id
389      * to make it clear that these are not the same thing. However, for
390      * easier debugging, the network id can be recovered by dropping the
391      * last decimal digit (at least until they get very, very, large).
392      */
sessionIdFromNetId(final int netId)393     public static int sessionIdFromNetId(final int netId) {
394         if (netId <= 0) return INVALID_SESSION_ID;
395         return (int) (((long) netId * 10 + (8 - (netId % 9))) % Integer.MAX_VALUE + 1);
396     }
397 
updateWifiMetrics(long now, int s2, int score)398     private void updateWifiMetrics(long now, int s2, int score) {
399         int netId = getCurrentNetId();
400 
401         mAggressiveConnectedScore.updateUsingWifiInfo(mWifiInfo, now);
402         int s1 = mAggressiveConnectedScore.generateScore();
403         logLinkMetrics(now, netId, s1, s2, score);
404 
405         if (score != mWifiInfo.getScore()) {
406             if (mVerboseLoggingEnabled) {
407                 Log.d(TAG, "report new wifi score " + score);
408             }
409             mWifiInfo.setScore(score);
410         }
411         mWifiMetrics.incrementWifiScoreCount(score);
412     }
413 
414     private static final double TIME_CONSTANT_MILLIS = 30.0e+3;
415     private static final long NUD_THROTTLE_MILLIS = 5000;
416     private long mLastKnownNudCheckTimeMillis = 0;
417     private int mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
418     private int mNudYes = 0;    // Counts when we voted for a NUD
419     private int mNudCount = 0;  // Counts when we were told a NUD was sent
420 
421     /**
422      * Recommends that a layer 3 check be done
423      *
424      * The caller can use this to (help) decide that an IP reachability check
425      * is desirable. The check is not done here; that is the caller's responsibility.
426      *
427      * @return true to indicate that an IP reachability check is recommended
428      */
shouldCheckIpLayer()429     public boolean shouldCheckIpLayer() {
430         int nud = mScoringParams.getNudKnob();
431         if (nud == 0) {
432             return false;
433         }
434         long millis = mClock.getWallClockMillis();
435         long deltaMillis = millis - mLastKnownNudCheckTimeMillis;
436         // Don't ever ask back-to-back - allow at least 5 seconds
437         // for the previous one to finish.
438         if (deltaMillis < NUD_THROTTLE_MILLIS) {
439             return false;
440         }
441         // nextNudBreach is the bar the score needs to cross before we ask for NUD
442         double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE;
443         if (mWifiConnectedNetworkScorerHolder == null) {
444             // nud is between 1 and 10 at this point
445             double deltaLevel = 11 - nud;
446             // If we were below threshold the last time we checked, then compute a new bar
447             // that starts down from there and decays exponentially back up to the steady-state
448             // bar. If 5 time constants have passed, we are 99% of the way there, so skip the math.
449             if (mLastKnownNudCheckScore < ConnectedScore.WIFI_TRANSITION_SCORE
450                     && deltaMillis < 5.0 * TIME_CONSTANT_MILLIS) {
451                 double a = Math.exp(-deltaMillis / TIME_CONSTANT_MILLIS);
452                 nextNudBreach =
453                         a * (mLastKnownNudCheckScore - deltaLevel) + (1.0 - a) * nextNudBreach;
454             }
455         }
456         if (mScore >= nextNudBreach) {
457             return false;
458         }
459         mNudYes++;
460         return true;
461     }
462 
463     /**
464      * Should be called when a reachability check has been issued
465      *
466      * When the caller has requested an IP reachability check, calling this will
467      * help to rate-limit requests via shouldCheckIpLayer()
468      */
noteIpCheck()469     public void noteIpCheck() {
470         long millis = mClock.getWallClockMillis();
471         mLastKnownNudCheckTimeMillis = millis;
472         mLastKnownNudCheckScore = mScore;
473         mNudCount++;
474     }
475 
476     /**
477      * Data for dumpsys
478      *
479      * These are stored as csv formatted lines
480      */
481     private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>();
482 
483     /**
484      * Data logging for dumpsys
485      */
logLinkMetrics(long now, int netId, int s1, int s2, int score)486     private void logLinkMetrics(long now, int netId, int s1, int s2, int score) {
487         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
488         double rssi = mWifiInfo.getRssi();
489         double filteredRssi = -1;
490         double rssiThreshold = -1;
491         if (mWifiConnectedNetworkScorerHolder == null) {
492             filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi();
493             rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold();
494         }
495         int freq = mWifiInfo.getFrequency();
496         int txLinkSpeed = mWifiInfo.getLinkSpeed();
497         int rxLinkSpeed = mWifiInfo.getRxLinkSpeedMbps();
498         double txSuccessRate = mWifiInfo.getSuccessfulTxPacketsPerSecond();
499         double txRetriesRate = mWifiInfo.getRetriedTxPacketsPerSecond();
500         double txBadRate = mWifiInfo.getLostTxPacketsPerSecond();
501         double rxSuccessRate = mWifiInfo.getSuccessfulRxPacketsPerSecond();
502         String s;
503         try {
504             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
505             s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
506                     "%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
507                     timestamp, mSessionNumber, netId,
508                     rssi, filteredRssi, rssiThreshold, freq, txLinkSpeed, rxLinkSpeed,
509                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
510                     mNudYes, mNudCount,
511                     s1, s2, score);
512         } catch (Exception e) {
513             Log.e(TAG, "format problem", e);
514             return;
515         }
516         synchronized (mLinkMetricsHistory) {
517             mLinkMetricsHistory.add(s);
518             while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
519                 mLinkMetricsHistory.removeFirst();
520             }
521         }
522     }
523 
524     /**
525      * Tag to be used in dumpsys request
526      */
527     public static final String DUMP_ARG = "WifiScoreReport";
528 
529     /**
530      * Dump logged signal strength and traffic measurements.
531      * @param fd unused
532      * @param pw PrintWriter for writing dump to
533      * @param args unused
534      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)535     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
536         LinkedList<String> history;
537         synchronized (mLinkMetricsHistory) {
538             history = new LinkedList<>(mLinkMetricsHistory);
539         }
540         pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,freq,txLinkSpeed,"
541                 + "rxLinkSpeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
542         for (String line : history) {
543             pw.println(line);
544         }
545         history.clear();
546     }
547 
548     /**
549      * Set a scorer for Wi-Fi connected network score handling.
550      * @param binder
551      * @param scorer
552      */
setWifiConnectedNetworkScorer(IBinder binder, IWifiConnectedNetworkScorer scorer)553     public boolean setWifiConnectedNetworkScorer(IBinder binder,
554             IWifiConnectedNetworkScorer scorer) {
555         if (binder == null || scorer == null) return false;
556         // Enforce that only a single scorer can be set successfully.
557         if (mWifiConnectedNetworkScorerHolder != null) {
558             Log.e(TAG, "Failed to set current scorer because one scorer is already set");
559             return false;
560         }
561         WifiConnectedNetworkScorerHolder scorerHolder =
562                 new WifiConnectedNetworkScorerHolder(binder, scorer);
563         if (!scorerHolder.linkScorerToDeath()) {
564             return false;
565         }
566         mWifiConnectedNetworkScorerHolder = scorerHolder;
567 
568         try {
569             scorer.onSetScoreUpdateObserver(mScoreUpdateObserver);
570         } catch (RemoteException e) {
571             Log.e(TAG, "Unable to set score update observer " + scorer, e);
572             revertToDefaultConnectedScorer();
573             return false;
574         }
575         // Disable AOSP scorer
576         mVelocityBasedConnectedScore = null;
577         mWifiMetrics.setIsExternalWifiScorerOn(true);
578         // If there is already a connection, start a new session
579         final int netId = getCurrentNetId();
580         if (netId > 0) {
581             startConnectedNetworkScorer(netId);
582         }
583         return true;
584     }
585 
586     /**
587      * Clear an existing scorer for Wi-Fi connected network score handling.
588      */
clearWifiConnectedNetworkScorer()589     public void clearWifiConnectedNetworkScorer() {
590         if (mWifiConnectedNetworkScorerHolder == null) {
591             return;
592         }
593         mWifiConnectedNetworkScorerHolder.reset();
594         revertToDefaultConnectedScorer();
595     }
596 
597     /**
598      * Start the registered Wi-Fi connected network scorer.
599      * @param netId identifies the current android.net.Network
600      */
startConnectedNetworkScorer(int netId)601     public void startConnectedNetworkScorer(int netId) {
602         final int sessionId = getCurrentSessionId();
603         if (mWifiConnectedNetworkScorerHolder == null
604                 || netId != getCurrentNetId()
605                 || sessionId == INVALID_SESSION_ID) {
606             Log.w(TAG, "Cannot start external scoring"
607                     + " netId=" + netId
608                     + " currentNetId=" + getCurrentNetId()
609                     + " sessionId=" + sessionId);
610             return;
611         }
612         mWifiConnectedNetworkScorerHolder.startSession(sessionId);
613         mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
614     }
615 
616     /**
617      * Stop the registered Wi-Fi connected network scorer.
618      */
stopConnectedNetworkScorer()619     public void stopConnectedNetworkScorer() {
620         mNetworkAgent = null;
621         if (mWifiConnectedNetworkScorerHolder == null) {
622             return;
623         }
624         mWifiConnectedNetworkScorerHolder.stopSession();
625 
626         long millis = mClock.getWallClockMillis();
627         // Blocklist the current BSS
628         if ((mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS)
629                 && ((millis - mLastScoreBreachLowTimeMillis)
630                         >= MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS)) {
631             mBssidBlocklistMonitor.blockBssidForDurationMs(mWifiInfo.getBSSID(),
632                     mWifiInfo.getSSID(),
633                     DURATION_TO_BLOCKLIST_BSSID_AFTER_FIRST_EXITING_MILLIS);
634             mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
635         }
636     }
637 
638     /**
639      * Set NetworkAgent
640      */
setNetworkAgent(NetworkAgent agent)641     public void setNetworkAgent(NetworkAgent agent) {
642         mNetworkAgent = agent;
643     }
644 
645     /**
646      * Get cached score
647      */
getScore()648     public int getScore() {
649         return mScore;
650     }
651 
652     /**
653      * Set interface name
654      * @param ifaceName
655      */
setInterfaceName(String ifaceName)656     public void setInterfaceName(String ifaceName) {
657         mInterfaceName = ifaceName;
658     }
659 
revertToDefaultConnectedScorer()660     private void revertToDefaultConnectedScorer() {
661         Log.d(TAG, "Using VelocityBasedConnectedScore");
662         mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(mScoringParams, mClock);
663         mWifiConnectedNetworkScorerHolder = null;
664         mWifiMetrics.setIsExternalWifiScorerOn(false);
665     }
666 }
667