blob: 390293328ae35a5f883a3121c09486cd1d1295b4 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.Uri;
import android.net.wifi.IScoreUpdateObserver;
import android.net.wifi.IWifiConnectedNetworkScorer;
import android.net.wifi.WifiInfo;
import android.net.wifi.nl80211.WifiNl80211Manager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wifi.resources.R;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Locale;
/**
* Class used to calculate scores for connected wifi networks and report it to the associated
* network agent.
*/
public class WifiScoreReport {
private static final String TAG = "WifiScoreReport";
private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 3600; // 3 hours on 3 second poll
private boolean mVerboseLoggingEnabled = false;
private static final long FIRST_REASONABLE_WALL_CLOCK = 1490000000000L; // mid-December 2016
private static final long MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS = 9000;
private long mLastDownwardBreachTimeMillis = 0;
private static final int WIFI_CONNECTED_NETWORK_SCORER_IDENTIFIER = 0;
private static final int INVALID_SESSION_ID = -1;
private static final long MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS = 29000;
private static final long INVALID_WALL_CLOCK_MILLIS = -1;
private static final int WIFI_SCORE_TO_TERMINATE_CONNECTION_BLOCKLIST_BSSID = -2;
/**
* Copy of the settings string. Can't directly use the constant because it is @hide.
* See {@link android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED}.
* TODO(b/167709538) remove this hardcoded string and create new API in Wifi mainline.
*/
@VisibleForTesting
public static final String SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED =
"adaptive_connectivity_enabled";
// Cache of the last score
private int mScore = ConnectedScore.WIFI_MAX_SCORE;
private final ScoringParams mScoringParams;
private final Clock mClock;
private int mSessionNumber = 0; // not to be confused with sessionid, this just counts resets
private String mInterfaceName;
private final BssidBlocklistMonitor mBssidBlocklistMonitor;
private final Context mContext;
private long mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
private long mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
ConnectedScore mAggressiveConnectedScore;
VelocityBasedConnectedScore mVelocityBasedConnectedScore;
NetworkAgent mNetworkAgent;
WifiMetrics mWifiMetrics;
WifiInfo mWifiInfo;
WifiNative mWifiNative;
WifiThreadRunner mWifiThreadRunner;
DeviceConfigFacade mDeviceConfigFacade;
Handler mHandler;
FrameworkFacade mFrameworkFacade;
/**
* Callback proxy. See {@link android.net.wifi.WifiManager.ScoreUpdateObserver}.
*/
private class ScoreUpdateObserverProxy extends IScoreUpdateObserver.Stub {
@Override
public void notifyScoreUpdate(int sessionId, int score) {
mWifiThreadRunner.post(() -> {
if (mWifiConnectedNetworkScorerHolder == null
|| sessionId == INVALID_SESSION_ID
|| sessionId != getCurrentSessionId()) {
Log.w(TAG, "Ignoring stale/invalid external score"
+ " sessionId=" + sessionId
+ " currentSessionId=" + getCurrentSessionId()
+ " score=" + score);
return;
}
// Disconnect WiFi and blocklist current BSSID. This is an intermediate solution
// and will be removed when the extension API is extended to include more inputs
// tracked by b/171571687.
if (score == WIFI_SCORE_TO_TERMINATE_CONNECTION_BLOCKLIST_BSSID) {
mBssidBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
mWifiInfo.getSSID(),
BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
mWifiInfo.getRssi());
return;
}
if (score > ConnectedScore.WIFI_MAX_SCORE
|| score < ConnectedScore.WIFI_MIN_SCORE) {
Log.e(TAG, "Invalid score value from external scorer: " + score);
return;
}
long millis = mClock.getWallClockMillis();
if (score < ConnectedScore.WIFI_TRANSITION_SCORE) {
if (mScore >= ConnectedScore.WIFI_TRANSITION_SCORE) {
mLastScoreBreachLowTimeMillis = millis;
}
} else {
mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
}
if (score > ConnectedScore.WIFI_TRANSITION_SCORE) {
if (mScore <= ConnectedScore.WIFI_TRANSITION_SCORE) {
mLastScoreBreachHighTimeMillis = millis;
}
} else {
mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
}
reportNetworkScoreToConnectivityServiceIfNecessary(score);
mScore = score;
updateWifiMetrics(millis, -1, mScore);
});
}
@Override
public void triggerUpdateOfWifiUsabilityStats(int sessionId) {
mWifiThreadRunner.post(() -> {
if (mWifiConnectedNetworkScorerHolder == null
|| sessionId == INVALID_SESSION_ID
|| sessionId != getCurrentSessionId()
|| mInterfaceName == null) {
Log.w(TAG, "Ignoring triggerUpdateOfWifiUsabilityStats"
+ " sessionId=" + sessionId
+ " currentSessionId=" + getCurrentSessionId()
+ " interfaceName=" + mInterfaceName);
return;
}
WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
// update mWifiInfo
// TODO(b/153075963): Better coordinate this class and ClientModeImpl to remove
// redundant codes below and in ClientModeImpl#fetchRssiLinkSpeedAndFrequencyNative.
WifiNl80211Manager.SignalPollResult pollResult =
mWifiNative.signalPoll(mInterfaceName);
if (pollResult != null) {
int newRssi = pollResult.currentRssiDbm;
int newTxLinkSpeed = pollResult.txBitrateMbps;
int newFrequency = pollResult.associationFrequencyMHz;
int newRxLinkSpeed = pollResult.rxBitrateMbps;
if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
if (newRssi > (WifiInfo.INVALID_RSSI + 256)) {
Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
newRssi -= 256;
}
mWifiInfo.setRssi(newRssi);
} else {
mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
}
/*
* set Tx link speed only if it is valid
*/
if (newTxLinkSpeed > 0) {
mWifiInfo.setLinkSpeed(newTxLinkSpeed);
mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
}
/*
* set Rx link speed only if it is valid
*/
if (newRxLinkSpeed > 0) {
mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
}
if (newFrequency > 0) {
mWifiInfo.setFrequency(newFrequency);
}
}
// TODO(b/153075963): This should not be plumbed through WifiMetrics
mWifiMetrics.updateWifiUsabilityStatsEntries(mWifiInfo, stats);
});
}
}
/**
* Report network score to connectivity service.
*/
private void reportNetworkScoreToConnectivityServiceIfNecessary(int score) {
if (mNetworkAgent == null) {
return;
}
if (mWifiConnectedNetworkScorerHolder == null && score == mWifiInfo.getScore()) {
return;
}
if (mWifiConnectedNetworkScorerHolder != null
&& mContext.getResources().getBoolean(
R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)) {
long millis = mClock.getWallClockMillis();
if (mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS) {
if (mWifiInfo.getRssi()
>= mDeviceConfigFacade.getRssiThresholdNotSendLowScoreToCsDbm()) {
Log.d(TAG, "Not reporting low score because RSSI is high "
+ mWifiInfo.getRssi());
return;
}
if ((millis - mLastScoreBreachLowTimeMillis)
< mDeviceConfigFacade.getMinConfirmationDurationSendLowScoreMs()) {
Log.d(TAG, "Not reporting low score because elapsed time is shorter than "
+ "the minimum confirmation duration");
return;
}
}
if (mLastScoreBreachHighTimeMillis != INVALID_WALL_CLOCK_MILLIS
&& (millis - mLastScoreBreachHighTimeMillis)
< mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()) {
Log.d(TAG, "Not reporting high score because elapsed time is shorter than "
+ "the minimum confirmation duration");
return;
}
}
// Stay a notch above the transition score if adaptive connectivity is disabled.
if (!mAdaptiveConnectivityEnabled) {
score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
if (mVerboseLoggingEnabled) {
Log.d(TAG,
"Adaptive connectivity disabled - Stay a notch above the transition score");
}
}
mNetworkAgent.sendNetworkScore(score);
}
/**
* Container for storing info about external scorer and tracking its death.
*/
private final class WifiConnectedNetworkScorerHolder implements IBinder.DeathRecipient {
private final IBinder mBinder;
private final IWifiConnectedNetworkScorer mScorer;
private int mSessionId = INVALID_SESSION_ID;
WifiConnectedNetworkScorerHolder(IBinder binder, IWifiConnectedNetworkScorer scorer) {
mBinder = binder;
mScorer = scorer;
}
/**
* Link WiFi connected scorer to death listener.
*/
public boolean linkScorerToDeath() {
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Unable to linkToDeath Wifi connected network scorer " + mScorer, e);
return false;
}
return true;
}
/**
* App hosting the binder has died.
*/
@Override
public void binderDied() {
mWifiThreadRunner.post(() -> revertToDefaultConnectedScorer());
}
/**
* Unlink this object from binder death.
*/
public void reset() {
mBinder.unlinkToDeath(this, 0);
}
/**
* Starts a new scoring session.
*/
public void startSession(int sessionId) {
if (sessionId == INVALID_SESSION_ID) {
throw new IllegalArgumentException();
}
if (mSessionId != INVALID_SESSION_ID) {
// This is not expected to happen, log if it does
Log.e(TAG, "Stopping session " + mSessionId + " before starting " + sessionId);
stopSession();
}
// Bail now if the scorer has gone away
if (this != mWifiConnectedNetworkScorerHolder) {
return;
}
mSessionId = sessionId;
try {
mScorer.onStart(sessionId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to start Wifi connected network scorer " + this, e);
revertToDefaultConnectedScorer();
}
}
public void stopSession() {
final int sessionId = mSessionId;
if (sessionId == INVALID_SESSION_ID) return;
mSessionId = INVALID_SESSION_ID;
try {
mScorer.onStop(sessionId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to stop Wifi connected network scorer " + this, e);
revertToDefaultConnectedScorer();
}
}
}
private final ScoreUpdateObserverProxy mScoreUpdateObserver =
new ScoreUpdateObserverProxy();
private WifiConnectedNetworkScorerHolder mWifiConnectedNetworkScorerHolder;
/**
* Observer for adaptive connectivity enable settings changes.
* This is enabled by default. Will be toggled off via adb command or a settings
* toggle by the user to disable adaptive connectivity.
*/
private class AdaptiveConnectivityEnabledSettingObserver extends ContentObserver {
AdaptiveConnectivityEnabledSettingObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
mAdaptiveConnectivityEnabled = getValue();
Log.d(TAG, "Adaptive connectivity status changed: " + mAdaptiveConnectivityEnabled);
mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
mWifiMetrics.logUserActionEvent(
mWifiMetrics.convertAdaptiveConnectivityStateToUserActionEventType(
mAdaptiveConnectivityEnabled));
}
/**
* Register settings change observer.
*/
public void initialize() {
Uri uri = Settings.Secure.getUriFor(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED);
if (uri == null) {
Log.e(TAG, "Adaptive connectivity user toggle does not exist in Settings");
return;
}
mFrameworkFacade.registerContentObserver(mContext, uri, true, this);
mAdaptiveConnectivityEnabled = mAdaptiveConnectivityEnabledSettingObserver.getValue();
mWifiMetrics.setAdaptiveConnectivityState(mAdaptiveConnectivityEnabled);
}
public boolean getValue() {
return mFrameworkFacade.getSecureIntegerSetting(
mContext, SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1;
}
}
private final AdaptiveConnectivityEnabledSettingObserver
mAdaptiveConnectivityEnabledSettingObserver;
private boolean mAdaptiveConnectivityEnabled = true;
WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics,
WifiInfo wifiInfo, WifiNative wifiNative, BssidBlocklistMonitor bssidBlocklistMonitor,
WifiThreadRunner wifiThreadRunner, DeviceConfigFacade deviceConfigFacade,
Context context, Looper looper, FrameworkFacade frameworkFacade) {
mScoringParams = scoringParams;
mClock = clock;
mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(scoringParams, clock);
mWifiMetrics = wifiMetrics;
mWifiInfo = wifiInfo;
mWifiNative = wifiNative;
mBssidBlocklistMonitor = bssidBlocklistMonitor;
mWifiThreadRunner = wifiThreadRunner;
mDeviceConfigFacade = deviceConfigFacade;
mContext = context;
mFrameworkFacade = frameworkFacade;
mHandler = new Handler(looper);
mAdaptiveConnectivityEnabledSettingObserver =
new AdaptiveConnectivityEnabledSettingObserver(mHandler);
}
/**
* Reset the last calculated score.
*/
public void reset() {
mSessionNumber++;
mScore = ConnectedScore.WIFI_MAX_SCORE;
mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
mAggressiveConnectedScore.reset();
if (mVelocityBasedConnectedScore != null) {
mVelocityBasedConnectedScore.reset();
}
mLastDownwardBreachTimeMillis = 0;
mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
}
/**
* Enable/Disable verbose logging in score report generation.
*/
public void enableVerboseLogging(boolean enable) {
mVerboseLoggingEnabled = enable;
}
/**
* Calculate wifi network score based on updated link layer stats and send the score to
* the WifiNetworkAgent.
*
* If the score has changed from the previous value, update the WifiNetworkAgent.
*
* Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds.
*/
public void calculateAndReportScore() {
// Bypass AOSP scorer if Wifi connected network scorer is set
if (mWifiConnectedNetworkScorerHolder != null) {
return;
}
if (mWifiInfo.getRssi() == mWifiInfo.INVALID_RSSI) {
Log.d(TAG, "Not reporting score because RSSI is invalid");
return;
}
int score;
long millis = mClock.getWallClockMillis();
mVelocityBasedConnectedScore.updateUsingWifiInfo(mWifiInfo, millis);
int s2 = mVelocityBasedConnectedScore.generateScore();
score = s2;
if (mWifiInfo.getScore() > ConnectedScore.WIFI_TRANSITION_SCORE
&& score <= ConnectedScore.WIFI_TRANSITION_SCORE
&& mWifiInfo.getSuccessfulTxPacketsPerSecond()
>= mScoringParams.getYippeeSkippyPacketsPerSecond()
&& mWifiInfo.getSuccessfulRxPacketsPerSecond()
>= mScoringParams.getYippeeSkippyPacketsPerSecond()
) {
score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
}
if (mWifiInfo.getScore() > ConnectedScore.WIFI_TRANSITION_SCORE
&& score <= ConnectedScore.WIFI_TRANSITION_SCORE) {
// We don't want to trigger a downward breach unless the rssi is
// below the entry threshold. There is noise in the measured rssi, and
// the kalman-filtered rssi is affected by the trend, so check them both.
// TODO(b/74613347) skip this if there are other indications to support the low score
int entry = mScoringParams.getEntryRssi(mWifiInfo.getFrequency());
if (mVelocityBasedConnectedScore.getFilteredRssi() >= entry
|| mWifiInfo.getRssi() >= entry) {
// Stay a notch above the transition score to reduce ambiguity.
score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
}
}
if (mWifiInfo.getScore() >= ConnectedScore.WIFI_TRANSITION_SCORE
&& score < ConnectedScore.WIFI_TRANSITION_SCORE) {
mLastDownwardBreachTimeMillis = millis;
} else if (mWifiInfo.getScore() < ConnectedScore.WIFI_TRANSITION_SCORE
&& score >= ConnectedScore.WIFI_TRANSITION_SCORE) {
// Staying at below transition score for a certain period of time
// to prevent going back to wifi network again in a short time.
long elapsedMillis = millis - mLastDownwardBreachTimeMillis;
if (elapsedMillis < MIN_TIME_TO_KEEP_BELOW_TRANSITION_SCORE_MILLIS) {
score = mWifiInfo.getScore();
}
}
//sanitize boundaries
if (score > ConnectedScore.WIFI_MAX_SCORE) {
score = ConnectedScore.WIFI_MAX_SCORE;
}
if (score < 0) {
score = 0;
}
//report score
reportNetworkScoreToConnectivityServiceIfNecessary(score);
updateWifiMetrics(millis, s2, score);
mScore = score;
}
private int getCurrentNetId() {
int netId = 0;
if (mNetworkAgent != null) {
final Network network = mNetworkAgent.getNetwork();
if (network != null) {
netId = network.getNetId();
}
}
return netId;
}
private int getCurrentSessionId() {
return sessionIdFromNetId(getCurrentNetId());
}
/**
* Encodes a network id into a scoring session id.
*
* We use a different numeric value for session id and the network id
* to make it clear that these are not the same thing. However, for
* easier debugging, the network id can be recovered by dropping the
* last decimal digit (at least until they get very, very, large).
*/
public static int sessionIdFromNetId(final int netId) {
if (netId <= 0) return INVALID_SESSION_ID;
return (int) (((long) netId * 10 + (8 - (netId % 9))) % Integer.MAX_VALUE + 1);
}
private void updateWifiMetrics(long now, int s2, int score) {
int netId = getCurrentNetId();
mAggressiveConnectedScore.updateUsingWifiInfo(mWifiInfo, now);
int s1 = mAggressiveConnectedScore.generateScore();
logLinkMetrics(now, netId, s1, s2, score);
if (score != mWifiInfo.getScore()) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "report new wifi score " + score);
}
mWifiInfo.setScore(score);
}
mWifiMetrics.incrementWifiScoreCount(score);
}
private static final double TIME_CONSTANT_MILLIS = 30.0e+3;
private static final long NUD_THROTTLE_MILLIS = 5000;
private long mLastKnownNudCheckTimeMillis = 0;
private int mLastKnownNudCheckScore = ConnectedScore.WIFI_TRANSITION_SCORE;
private int mNudYes = 0; // Counts when we voted for a NUD
private int mNudCount = 0; // Counts when we were told a NUD was sent
/**
* Recommends that a layer 3 check be done
*
* The caller can use this to (help) decide that an IP reachability check
* is desirable. The check is not done here; that is the caller's responsibility.
*
* @return true to indicate that an IP reachability check is recommended
*/
public boolean shouldCheckIpLayer() {
// Don't recommend if adaptive connectivity is disabled.
if (!mAdaptiveConnectivityEnabled) {
if (mVerboseLoggingEnabled) {
Log.d(TAG, "Adaptive connectivity disabled - Don't check IP layer");
}
return false;
}
int nud = mScoringParams.getNudKnob();
if (nud == 0) {
return false;
}
long millis = mClock.getWallClockMillis();
long deltaMillis = millis - mLastKnownNudCheckTimeMillis;
// Don't ever ask back-to-back - allow at least 5 seconds
// for the previous one to finish.
if (deltaMillis < NUD_THROTTLE_MILLIS) {
return false;
}
// nextNudBreach is the bar the score needs to cross before we ask for NUD
double nextNudBreach = ConnectedScore.WIFI_TRANSITION_SCORE;
if (mWifiConnectedNetworkScorerHolder == null) {
// nud is between 1 and 10 at this point
double deltaLevel = 11 - nud;
// If we were below threshold the last time we checked, then compute a new bar
// that starts down from there and decays exponentially back up to the steady-state
// bar. If 5 time constants have passed, we are 99% of the way there, so skip the math.
if (mLastKnownNudCheckScore < ConnectedScore.WIFI_TRANSITION_SCORE
&& deltaMillis < 5.0 * TIME_CONSTANT_MILLIS) {
double a = Math.exp(-deltaMillis / TIME_CONSTANT_MILLIS);
nextNudBreach =
a * (mLastKnownNudCheckScore - deltaLevel) + (1.0 - a) * nextNudBreach;
}
}
if (mScore >= nextNudBreach) {
return false;
}
mNudYes++;
return true;
}
/**
* Should be called when a reachability check has been issued
*
* When the caller has requested an IP reachability check, calling this will
* help to rate-limit requests via shouldCheckIpLayer()
*/
public void noteIpCheck() {
long millis = mClock.getWallClockMillis();
mLastKnownNudCheckTimeMillis = millis;
mLastKnownNudCheckScore = mScore;
mNudCount++;
}
/**
* Data for dumpsys
*
* These are stored as csv formatted lines
*/
private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>();
/**
* Data logging for dumpsys
*/
private void logLinkMetrics(long now, int netId, int s1, int s2, int score) {
if (now < FIRST_REASONABLE_WALL_CLOCK) return;
double rssi = mWifiInfo.getRssi();
double filteredRssi = -1;
double rssiThreshold = -1;
if (mWifiConnectedNetworkScorerHolder == null) {
filteredRssi = mVelocityBasedConnectedScore.getFilteredRssi();
rssiThreshold = mVelocityBasedConnectedScore.getAdjustedRssiThreshold();
}
int freq = mWifiInfo.getFrequency();
int txLinkSpeed = mWifiInfo.getLinkSpeed();
int rxLinkSpeed = mWifiInfo.getRxLinkSpeedMbps();
double txSuccessRate = mWifiInfo.getSuccessfulTxPacketsPerSecond();
double txRetriesRate = mWifiInfo.getRetriedTxPacketsPerSecond();
double txBadRate = mWifiInfo.getLostTxPacketsPerSecond();
double rxSuccessRate = mWifiInfo.getSuccessfulRxPacketsPerSecond();
String s;
try {
String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
"%s,%d,%d,%.1f,%.1f,%.1f,%d,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d",
timestamp, mSessionNumber, netId,
rssi, filteredRssi, rssiThreshold, freq, txLinkSpeed, rxLinkSpeed,
txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
mNudYes, mNudCount,
s1, s2, score);
} catch (Exception e) {
Log.e(TAG, "format problem", e);
return;
}
synchronized (mLinkMetricsHistory) {
mLinkMetricsHistory.add(s);
while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
mLinkMetricsHistory.removeFirst();
}
}
}
/**
* Tag to be used in dumpsys request
*/
public static final String DUMP_ARG = "WifiScoreReport";
/**
* Dump logged signal strength and traffic measurements.
* @param fd unused
* @param pw PrintWriter for writing dump to
* @param args unused
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
LinkedList<String> history;
synchronized (mLinkMetricsHistory) {
history = new LinkedList<>(mLinkMetricsHistory);
}
pw.println("time,session,netid,rssi,filtered_rssi,rssi_threshold,freq,txLinkSpeed,"
+ "rxLinkSpeed,tx_good,tx_retry,tx_bad,rx_pps,nudrq,nuds,s1,s2,score");
for (String line : history) {
pw.println(line);
}
history.clear();
}
/**
* Set a scorer for Wi-Fi connected network score handling.
* @param binder
* @param scorer
*/
public boolean setWifiConnectedNetworkScorer(IBinder binder,
IWifiConnectedNetworkScorer scorer) {
if (binder == null || scorer == null) return false;
// Enforce that only a single scorer can be set successfully.
if (mWifiConnectedNetworkScorerHolder != null) {
Log.e(TAG, "Failed to set current scorer because one scorer is already set");
return false;
}
WifiConnectedNetworkScorerHolder scorerHolder =
new WifiConnectedNetworkScorerHolder(binder, scorer);
if (!scorerHolder.linkScorerToDeath()) {
return false;
}
mWifiConnectedNetworkScorerHolder = scorerHolder;
try {
scorer.onSetScoreUpdateObserver(mScoreUpdateObserver);
} catch (RemoteException e) {
Log.e(TAG, "Unable to set score update observer " + scorer, e);
revertToDefaultConnectedScorer();
return false;
}
// Disable AOSP scorer
mVelocityBasedConnectedScore = null;
mWifiMetrics.setIsExternalWifiScorerOn(true);
// If there is already a connection, start a new session
final int netId = getCurrentNetId();
if (netId > 0) {
startConnectedNetworkScorer(netId);
}
return true;
}
/**
* Clear an existing scorer for Wi-Fi connected network score handling.
*/
public void clearWifiConnectedNetworkScorer() {
if (mWifiConnectedNetworkScorerHolder == null) {
return;
}
mWifiConnectedNetworkScorerHolder.reset();
revertToDefaultConnectedScorer();
}
/**
* Start the registered Wi-Fi connected network scorer.
* @param netId identifies the current android.net.Network
*/
public void startConnectedNetworkScorer(int netId) {
final int sessionId = getCurrentSessionId();
if (mWifiConnectedNetworkScorerHolder == null
|| netId != getCurrentNetId()
|| sessionId == INVALID_SESSION_ID) {
Log.w(TAG, "Cannot start external scoring"
+ " netId=" + netId
+ " currentNetId=" + getCurrentNetId()
+ " sessionId=" + sessionId);
return;
}
mWifiInfo.setScore(ConnectedScore.WIFI_MAX_SCORE);
mWifiConnectedNetworkScorerHolder.startSession(sessionId);
mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
mLastScoreBreachHighTimeMillis = INVALID_WALL_CLOCK_MILLIS;
}
/**
* Stop the registered Wi-Fi connected network scorer.
*/
public void stopConnectedNetworkScorer() {
mNetworkAgent = null;
if (mWifiConnectedNetworkScorerHolder == null) {
return;
}
mWifiConnectedNetworkScorerHolder.stopSession();
long millis = mClock.getWallClockMillis();
// Blocklist the current BSS
if ((mLastScoreBreachLowTimeMillis != INVALID_WALL_CLOCK_MILLIS)
&& ((millis - mLastScoreBreachLowTimeMillis)
>= MIN_TIME_TO_WAIT_BEFORE_BLOCKLIST_BSSID_MILLIS)) {
mBssidBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
mWifiInfo.getSSID(),
BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE,
mWifiInfo.getRssi());
mLastScoreBreachLowTimeMillis = INVALID_WALL_CLOCK_MILLIS;
}
}
/**
* Set NetworkAgent
*/
public void setNetworkAgent(NetworkAgent agent) {
mNetworkAgent = agent;
}
/**
* Get cached score
*/
public int getScore() {
return mScore;
}
/**
* Set interface name
* @param ifaceName
*/
public void setInterfaceName(String ifaceName) {
mInterfaceName = ifaceName;
}
private void revertToDefaultConnectedScorer() {
Log.d(TAG, "Using VelocityBasedConnectedScore");
mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(mScoringParams, mClock);
mWifiConnectedNetworkScorerHolder = null;
mWifiMetrics.setIsExternalWifiScorerOn(false);
}
/**
* Initialize WifiScoreReport
*/
public void initialize() {
mAdaptiveConnectivityEnabledSettingObserver.initialize();
}
}