[RTT] CTS for the public Wi-Fi RTT APIs

Baseline set of tests for the public Wi-Fi RTT API.

Note: some of these tests require an IEEE 802.11mc capable AP.

Bug: 63446747
Test: tests pass
Change-Id: I176d9adbef15ce1c33b4572d5eb7e6cdf672f021
This commit is contained in:
Etan Cohen
2018-02-28 16:25:22 -08:00
parent c548e56342
commit 439c05ce8b
3 changed files with 445 additions and 0 deletions

View File

@@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2018 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 android.net.wifi.rtt.cts;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.RangingResultCallback;
import android.net.wifi.rtt.WifiRttManager;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.test.AndroidTestCase;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* Base class for Wi-Fi RTT CTS test cases. Provides a uniform configuration and event management
* facility.
*/
public class TestBase extends AndroidTestCase {
protected static final String TAG = "WifiRttCtsTests";
// The SSID of the test AP which supports IEEE 802.11mc
// TODO b/74518964: finalize correct method to refer to an AP in the test lab
static final String SSID_OF_TEST_AP = "standalone_rtt";
// wait for Wi-Fi RTT to become available
private static final int WAIT_FOR_RTT_CHANGE_SECS = 10;
// wait for Wi-Fi scan results to become available
private static final int WAIT_FOR_SCAN_RESULTS_SECS = 20;
protected WifiRttManager mWifiRttManager;
protected WifiManager mWifiManager;
private LocationManager mLocationManager;
private WifiManager.WifiLock mWifiLock;
private final HandlerThread mHandlerThread = new HandlerThread("SingleDeviceTest");
protected final Executor mExecutor;
{
mHandlerThread.start();
mExecutor = new HandlerExecutor(new Handler(mHandlerThread.getLooper()));
}
/**
* Returns a flag indicating whether or not Wi-Fi RTT should be tested. Wi-Fi RTT
* should be tested if the feature is supported on the current device.
*/
static boolean shouldTestWifiRtt(Context context) {
final PackageManager pm = context.getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_WIFI_RTT);
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!shouldTestWifiRtt(getContext())) {
return;
}
mLocationManager = (LocationManager) getContext().getSystemService(
Context.LOCATION_SERVICE);
assertTrue("RTT testing requires Location to be enabled",
mLocationManager.isLocationEnabled());
mWifiRttManager = (WifiRttManager) getContext().getSystemService(
Context.WIFI_RTT_RANGING_SERVICE);
assertNotNull("Wi-Fi RTT Manager", mWifiRttManager);
mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
assertNotNull("Wi-Fi Manager", mWifiManager);
mWifiLock = mWifiManager.createWifiLock(TAG);
mWifiLock.acquire();
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
WifiRttBroadcastReceiver receiver = new WifiRttBroadcastReceiver();
mContext.registerReceiver(receiver, intentFilter);
if (!mWifiRttManager.isAvailable()) {
assertTrue("Timeout waiting for Wi-Fi RTT to change status",
receiver.waitForStateChange());
assertTrue("Wi-Fi RTT is not available (should be)", mWifiRttManager.isAvailable());
}
}
@Override
protected void tearDown() throws Exception {
if (!shouldTestWifiRtt(getContext())) {
super.tearDown();
return;
}
super.tearDown();
}
class WifiRttBroadcastReceiver extends BroadcastReceiver {
private CountDownLatch mBlocker = new CountDownLatch(1);
@Override
public void onReceive(Context context, Intent intent) {
if (WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED.equals(intent.getAction())) {
mBlocker.countDown();
}
}
boolean waitForStateChange() throws InterruptedException {
return mBlocker.await(WAIT_FOR_RTT_CHANGE_SECS, TimeUnit.SECONDS);
}
}
class WifiScansBroadcastReceiver extends BroadcastReceiver {
private CountDownLatch mBlocker = new CountDownLatch(1);
@Override
public void onReceive(Context context, Intent intent) {
if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
mBlocker.countDown();
}
}
boolean waitForStateChange() throws InterruptedException {
return mBlocker.await(WAIT_FOR_SCAN_RESULTS_SECS, TimeUnit.SECONDS);
}
}
class ResultCallback extends RangingResultCallback {
private CountDownLatch mBlocker = new CountDownLatch(1);
private int mCode; // 0: success, otherwise RangingResultCallback STATUS_CODE_*.
private List<RangingResult> mResults;
@Override
public void onRangingFailure(int code) {
mCode = code;
mResults = null; // not necessary since intialized to null - but for completeness
mBlocker.countDown();
}
@Override
public void onRangingResults(List<RangingResult> results) {
mCode = 0; // not necessary since initialized to 0 - but for completeness
mResults = results;
mBlocker.countDown();
}
/**
* Waits for the listener callback to be called - or an error (timeout, interruption).
* Returns true on callback called, false on error (timeout, interruption).
*/
boolean waitForCallback() throws InterruptedException {
return mBlocker.await(WAIT_FOR_RTT_CHANGE_SECS, TimeUnit.SECONDS);
}
/**
* Returns the code of the callback operation. Will be 0 for success (onRangingResults
* called), else (if onRangingFailure called) will be one of the STATUS_CODE_* values.
*/
int getCode() {
return mCode;
}
/**
* Returns the list of ranging results. In cases of error (getCode() != 0) will return null.
*/
List<RangingResult> getResults() {
return mResults;
}
}
/**
* Start a scan and return a list of observed ScanResults (APs).
*/
protected List<ScanResult> scanAps() throws InterruptedException {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
WifiScansBroadcastReceiver receiver = new WifiScansBroadcastReceiver();
mContext.registerReceiver(receiver, intentFilter);
mWifiManager.startScan();
receiver.waitForStateChange();
mContext.unregisterReceiver(receiver);
return mWifiManager.getScanResults();
}
/**
* Start a scan and return the test AP with the specified SSID and which supports IEEE 802.11mc.
* If the AP is not found re-attempts the scan maxScanRetries times (i.e. total number of
* scans can be maxScanRetries + 1).
*
* Returns null if test AP is not found in the specified number of scans.
*
* @param ssid The SSID of the test AP
* @param maxScanRetries Maximum number of scans retries (in addition to first scan).
*/
protected ScanResult scanForTestAp(String ssid, int maxScanRetries)
throws InterruptedException {
int scanCount = 0;
while (scanCount <= maxScanRetries) {
for (ScanResult scanResult : scanAps()) {
if (!scanResult.is80211mcResponder()) {
continue;
}
if (!ssid.equals(scanResult.SSID)) {
continue;
}
return scanResult;
}
scanCount++;
}
return null;
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright (C) 2018 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 android.net.wifi.rtt.cts;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.rtt.RangingRequest;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.WifiRttManager;
import com.android.compatibility.common.util.DeviceReportLog;
import com.android.compatibility.common.util.ResultType;
import com.android.compatibility.common.util.ResultUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Wi-Fi RTT CTS test: range to all available Access Points which support IEEE 802.11mc.
*/
public class WifiRttTest extends TestBase {
// Max number of scan retries to do while searching for APs supporting IEEE 802.11mc
private static final int MAX_NUM_SCAN_RETRIES_SEARCHING_FOR_IEEE80211MC_AP = 2;
// Number of RTT measurements per AP
private static final int NUM_OF_RTT_ITERATIONS = 10;
// Maximum failure rate of RTT measurements (percentage)
private static final int MAX_FAILURE_RATE_PERCENT = 10;
// Maximum variation from the average measurement (measures consistency)
private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 1000;
// Minimum valid RSSI value
private static final int MIN_VALID_RSSI = -100;
/**
* Test Wi-Fi RTT ranging operation:
* - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
* - Perform N (constant) RTT operations
* - Validate:
* - Failure ratio < threshold (constant)
* - Result margin < threshold (constant)
*/
public void testRangingToTestAp() throws InterruptedException {
if (!shouldTestWifiRtt(getContext())) {
return;
}
// Scan for IEEE 802.11mc supporting APs
ScanResult testAp = scanForTestAp(SSID_OF_TEST_AP,
MAX_NUM_SCAN_RETRIES_SEARCHING_FOR_IEEE80211MC_AP);
assertTrue("Cannot find test AP", testAp != null);
// Perform RTT operations
RangingRequest request = new RangingRequest.Builder().addAccessPoint(testAp).build();
List<RangingResult> allResults = new ArrayList<>();
int numFailures = 0;
int distanceSum = 0;
int distanceMin = 0;
int distanceMax = 0;
int[] statuses = new int[NUM_OF_RTT_ITERATIONS];
int[] distanceMms = new int[NUM_OF_RTT_ITERATIONS];
int[] distanceStdDevMms = new int[NUM_OF_RTT_ITERATIONS];
int[] rssis = new int[NUM_OF_RTT_ITERATIONS];
for (int i = 0; i < NUM_OF_RTT_ITERATIONS; ++i) {
ResultCallback callback = new ResultCallback();
mWifiRttManager.startRanging(request, mExecutor, callback);
assertTrue("Wi-Fi RTT results: no callback on iteration " + i,
callback.waitForCallback());
List<RangingResult> currentResults = callback.getResults();
assertTrue("Wi-Fi RTT results: null results (onRangingFailure) on iteration " + i,
currentResults != null);
assertTrue("Wi-Fi RTT results: unexpected # of results (expect 1) on iteration " + i,
currentResults.size() == 1);
RangingResult result = currentResults.get(0);
assertTrue("Wi-Fi RTT results: invalid result (wrong BSSID) entry on iteration " + i,
result.getMacAddress().toString().equals(testAp.BSSID));
allResults.add(result);
int status = result.getStatus();
statuses[i] = status;
if (status == RangingResult.STATUS_SUCCESS) {
distanceSum += result.getDistanceMm();
if (i == 0) {
distanceMin = result.getDistanceMm();
distanceMax = result.getDistanceMm();
} else {
distanceMin = Math.min(distanceMin, result.getDistanceMm());
distanceMax = Math.max(distanceMax, result.getDistanceMm());
}
assertTrue("Wi-Fi RTT results: invalid RSSI on iteration " + i,
result.getRssi() >= MIN_VALID_RSSI);
distanceMms[i - numFailures] = result.getDistanceMm();
distanceStdDevMms[i - numFailures] = result.getDistanceStdDevMm();
rssis[i - numFailures] = result.getRssi();
} else {
numFailures++;
}
}
// Save results to log
int numGoodResults = NUM_OF_RTT_ITERATIONS - numFailures;
DeviceReportLog reportLog = new DeviceReportLog(TAG, "testRangingToTestAp");
reportLog.addValues("status_codes", statuses, ResultType.NEUTRAL, ResultUnit.NONE);
reportLog.addValues("distance_mm", Arrays.copyOf(distanceMms, numGoodResults),
ResultType.NEUTRAL, ResultUnit.NONE);
reportLog.addValues("distance_stddev_mm", Arrays.copyOf(distanceStdDevMms, numGoodResults),
ResultType.NEUTRAL, ResultUnit.NONE);
reportLog.addValues("rssi_dbm", Arrays.copyOf(rssis, numGoodResults), ResultType.NEUTRAL,
ResultUnit.NONE);
reportLog.submit();
// Analyze results
assertTrue("Wi-Fi RTT failure rate exceeds threshold",
numFailures <= NUM_OF_RTT_ITERATIONS * MAX_FAILURE_RATE_PERCENT / 100);
if (numFailures != NUM_OF_RTT_ITERATIONS) {
double distanceAvg = distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold",
(distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold",
(distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
}
}
/**
* Validate that on Wi-Fi RTT availability change we get a broadcast + the API returns
* correct status.
*/
public void testAvailabilityStatusChange() throws Exception {
if (!shouldTestWifiRtt(getContext())) {
return;
}
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
// 1. Disable Wi-Fi
WifiRttBroadcastReceiver receiver1 = new WifiRttBroadcastReceiver();
mContext.registerReceiver(receiver1, intentFilter);
mWifiManager.setWifiEnabled(false);
assertTrue("Timeout waiting for Wi-Fi RTT to change status",
receiver1.waitForStateChange());
assertFalse("Wi-Fi RTT is available (should not be)", mWifiRttManager.isAvailable());
// 2. Enable Wi-Fi
WifiRttBroadcastReceiver receiver2 = new WifiRttBroadcastReceiver();
mContext.registerReceiver(receiver2, intentFilter);
mWifiManager.setWifiEnabled(true);
assertTrue("Timeout waiting for Wi-Fi RTT to change status",
receiver2.waitForStateChange());
assertTrue("Wi-Fi RTT is not available (should be)", mWifiRttManager.isAvailable());
}
/**
* Validate that when a request contains more range operations than allowed (by API) that we
* get an exception.
*/
public void testRequestTooLarge() {
if (!shouldTestWifiRtt(getContext())) {
return;
}
RangingRequest.Builder builder = new RangingRequest.Builder();
for (int i = 0; i < RangingRequest.getMaxPeers() + 1; ++i) {
ScanResult dummy = new ScanResult();
dummy.BSSID = "00:01:02:03:04:05";
builder.addAccessPoint(dummy);
}
try {
mWifiRttManager.startRanging(builder.build(), mExecutor, new ResultCallback());
} catch (IllegalArgumentException e) {
return;
}
assertTrue(
"Did not receive expected IllegalArgumentException when tried to range to too "
+ "many peers",
false);
}
}