/* * 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.networkrecommendation; import android.content.Context; import android.net.NetworkKey; import android.net.NetworkRecommendationProvider; import android.net.NetworkScoreManager; import android.net.RecommendationRequest; import android.net.RecommendationResult; import android.net.RssiCurve; import android.net.ScoredNetwork; import android.net.WifiKey; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.os.Bundle; import android.support.annotation.VisibleForTesting; import android.text.TextUtils; import android.util.ArrayMap; import com.android.networkrecommendation.util.Blog; import com.android.networkrecommendation.util.SsidUtil; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; import javax.annotation.concurrent.GuardedBy; /** * In memory, debuggable network recommendation provider. * *
This example evaluates networks in a scan and picks the "least bad" network, returning a * result to the RecommendedNetworkEvaluator, regardless of configuration point. * *
This recommender is not yet recommended for non-development devices. * *
To debug: * $ adb shell dumpsys activity service NetworkRecommendationService * *
Clear stored scores: * $ adb shell dumpsys activity service NetworkRecommendationService clear * *
Score a network: * $ adb shell dumpsys activity service NetworkRecommendationService addScore $SCORE * *
SCORE: "Quoted SSID",bssid|$RSSI_CURVE|metered|captivePortal|BADGE * *
RSSI_CURVE: bucketWidth,score,score,score,score,... * *
curve, metered and captive portal are optional, as expressed by an empty value. * *
BADGE: NONE, SD, HD, 4K * *
All commands should be executed on one line, no spaces between each line of the command.. *
Eg, A high quality, paid network with captive portal: * $ adb shell dumpsys activity service NetworkRecommendationService addScore \ * '\"Metered\",aa:bb:cc:dd:ee:ff\| * 10,-128,-128,-128,-128,-128,-128,-128,-128,27,27,27,27,27,-128\|1\|1' * *
Eg, A high quality, unmetered network with captive portal: * $ adb shell dumpsys activity service NetworkRecommendationService addScore \ * '\"Captive\",aa:bb:cc:dd:ee:ff\| * 10,-128,-128,-128,-128,-128,-128,-128,-128,28,28,28,28,28,-128\|0\|1' * *
Eg, A high quality, unmetered network with any bssid:
* $ adb shell dumpsys activity service NetworkRecommendationService addScore \
* '\"AnySsid\",00:00:00:00:00:00\|
* 10,-128,-128,-128,-128,-128,-128,-128,-128,29,29,29,29,29,-128\|0\|0'
*/
@VisibleForTesting
public class DefaultNetworkRecommendationProvider
extends NetworkRecommendationProvider implements SynchronousNetworkRecommendationProvider {
static final String TAG = "DefaultNetRecProvider";
private static final String WILDCARD_MAC = "00:00:00:00:00:00";
/**
* The lowest RSSI value at which a fixed score should apply.
* Only used for development / testing purpose.
*/
@VisibleForTesting
static final int CONSTANT_CURVE_START = -150;
@VisibleForTesting
static final RssiCurve BADGE_CURVE_SD =
new RssiCurve(
CONSTANT_CURVE_START,
10 /* bucketWidth */,
new byte[] {0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10},
0 /* defaultActiveNetworkBoost */);
@VisibleForTesting
static final RssiCurve BADGE_CURVE_HD =
new RssiCurve(
CONSTANT_CURVE_START,
10 /* bucketWidth */,
new byte[] {0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20},
0 /* defaultActiveNetworkBoost */);
@VisibleForTesting
static final RssiCurve BADGE_CURVE_4K =
new RssiCurve(
CONSTANT_CURVE_START,
10 /* bucketWidth */,
new byte[] {0, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30},
0 /* defaultActiveNetworkBoost */);
private final NetworkScoreManager mScoreManager;
private final ScoreStorage mStorage;
private final Object mStatsLock = new Object();
@GuardedBy("mStatsLock")
private int mRecommendationCounter = 0;
@GuardedBy("mStatsLock")
private WifiConfiguration mLastRecommended = null;
@GuardedBy("mStatsLock")
private int mScoreCounter = 0;
public DefaultNetworkRecommendationProvider(Context context, Executor executor,
NetworkScoreManager scoreManager, ScoreStorage storage) {
super(context, executor);
mScoreManager = scoreManager;
mStorage = storage;
}
/**
* Recommend the wireless network with the highest RSSI and run
* {@link ResultCallback#onResult(RecommendationResult)}.
*/
@Override
public void onRequestRecommendation(RecommendationRequest request,
ResultCallback callback) {
callback.onResult(requestRecommendation(request));
}
@Override
/** Recommend the wireless network with the highest RSSI. */
public RecommendationResult requestRecommendation(RecommendationRequest request) {
ScanResult recommendedScanResult = null;
int recommendedScore = Integer.MIN_VALUE;
ScanResult[] results = request.getScanResults();
if (results != null) {
for (int i = 0; i < results.length; i++) {
final ScanResult scanResult = results[i];
Blog.v(TAG, "Scan: " + scanResult + " " + i);
// We only want to recommend open networks. This check is taken from
// places like WifiNotificationController and will be extracted to ScanResult in
// a future CL.
if (!"[ESS]".equals(scanResult.capabilities)) {
Blog.v(TAG, "Discarding closed network: " + scanResult);
continue;
}
final NetworkKey networkKey = new NetworkKey(
new WifiKey(SsidUtil.quoteSsid(scanResult.SSID),
scanResult.BSSID));
Blog.v(TAG, "Evaluating network: " + networkKey);
// We will only score networks we know about.
final ScoredNetwork network = mStorage.get(networkKey);
if (network == null) {
Blog.v(TAG, "Discarding unscored network: " + scanResult);
continue;
}
final int score = network.rssiCurve.lookupScore(scanResult.level);
Blog.v(TAG, "Scored " + scanResult + ": " + score);
if (score > recommendedScore) {
recommendedScanResult = scanResult;
recommendedScore = score;
Blog.v(TAG, "New recommended network: " + scanResult);
continue;
}
}
} else {
Blog.w(TAG, "Received null scan results in request.");
}
// If we ended up without a recommendation, recommend the provided configuration
// instead. If we wanted the platform to avoid this network, too, we could send back an
// empty recommendation.
RecommendationResult recommendationResult;
if (recommendedScanResult == null) {
if (request.getDefaultWifiConfig() != null) {
recommendationResult = RecommendationResult
.createConnectRecommendation(request.getDefaultWifiConfig());
} else {
recommendationResult = RecommendationResult.createDoNotConnectRecommendation();
}
} else {
// Build a configuration based on the scan.
WifiConfiguration recommendedConfig = new WifiConfiguration();
recommendedConfig.SSID = SsidUtil.quoteSsid(recommendedScanResult.SSID);
recommendedConfig.BSSID = recommendedScanResult.BSSID;
recommendedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
recommendationResult = RecommendationResult
.createConnectRecommendation(recommendedConfig);
}
synchronized (mStatsLock) {
mLastRecommended = recommendationResult.getWifiConfiguration();
mRecommendationCounter++;
Blog.d(TAG, "Recommending network: " + configToString(mLastRecommended));
}
return recommendationResult;
}
/** Score networks based on a few properties ... */
@Override
public void onRequestScores(NetworkKey[] networks) {
synchronized (mStatsLock) {
mScoreCounter++;
}
List