blob: a93828880f26a4bf04aa82b519891962286f856d [file] [log] [blame]
/*
* Copyright (C) 2020 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.google.android.chre.test.crossvalidator;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubManager;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import com.google.android.chre.nanoapp.proto.ChreCrossValidationWifi;
import com.google.android.chre.nanoapp.proto.ChreCrossValidationWifi.Step;
import com.google.android.chre.nanoapp.proto.ChreTestCommon;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Assert;
import org.junit.Assume;
import java.math.BigInteger;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class ChreCrossValidatorWifi extends ChreCrossValidatorBase {
private static final long AWAIT_STEP_RESULT_MESSAGE_TIMEOUT_MS = 1000; // 1 sec
private static final long AWAIT_WIFI_SCAN_RESULT_TIMEOUT_MS = 3000; // 3 sec
private static final long NANO_APP_ID = 0x476f6f6754000005L;
/**
* Wifi capabilities flags listed in
* //system/chre/chre_api/include/chre_api/chre/wifi.h
*/
private static final int WIFI_CAPABILITIES_SCAN_MONITORING = 1;
private static final int WIFI_CAPABILITIES_ON_DEMAND_SCAN = 2;
AtomicReference<Step> mStep = new AtomicReference<Step>(Step.INIT);
AtomicBoolean mDidReceiveNanoAppMessage = new AtomicBoolean(false);
private AtomicBoolean mApWifiScanSuccess = new AtomicBoolean(false);
private CountDownLatch mAwaitApWifiSetupScan = new CountDownLatch(1);
private WifiManager mWifiManager;
private BroadcastReceiver mWifiScanReceiver;
private AtomicReference<ChreCrossValidationWifi.WifiCapabilities> mWifiCapabilities =
new AtomicReference<ChreCrossValidationWifi.WifiCapabilities>(null);
private AtomicBoolean mWifiScanResultsCompareFinalResult = new AtomicBoolean(false);
private AtomicReference<String> mWifiScanResultsCompareFinalErrorMessage =
new AtomicReference<String>(null);
public ChreCrossValidatorWifi(
ContextHubManager contextHubManager, ContextHubInfo contextHubInfo,
NanoAppBinary nanoAppBinary) {
super(contextHubManager, contextHubInfo, nanoAppBinary);
Assert.assertTrue("Nanoapp given to cross validator is not the designated chre cross"
+ " validation nanoapp.",
nanoAppBinary.getNanoAppId() == NANO_APP_ID);
Context context = InstrumentationRegistry.getInstrumentation().getContext();
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mWifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent intent) {
Log.i(TAG, "onReceive called");
boolean success = intent.getBooleanExtra(
WifiManager.EXTRA_RESULTS_UPDATED, false);
mApWifiScanSuccess.set(success);
mAwaitApWifiSetupScan.countDown();
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
context.registerReceiver(mWifiScanReceiver, intentFilter);
}
@Override public void validate() throws AssertionError {
mCollectingData.set(true);
sendStepStartMessage(Step.CAPABILITIES);
waitForMessageFromNanoapp();
mCollectingData.set(false);
Assume.assumeTrue("Chre wifi is not enabled",
chreWifiHasCapabilities(mWifiCapabilities.get()));
mCollectingData.set(true);
sendStepStartMessage(Step.SETUP);
waitForMessageFromNanoapp();
mCollectingData.set(false);
mCollectingData.set(true);
sendStepStartMessage(Step.VALIDATE);
Assert.assertTrue("Wifi manager start scan failed", mWifiManager.startScan());
waitForApScanResults();
sendWifiScanResultsToChre();
waitForMessageFromNanoapp();
mCollectingData.set(false);
}
/**
* Send step start message to nanoapp.
*/
private void sendStepStartMessage(Step step) {
mStep.set(step);
sendMessageToNanoApp(makeStepStartMessage(step));
}
/**
* Send message to the nanoapp.
*/
private void sendMessageToNanoApp(NanoAppMessage message) {
int result = mContextHubClient.sendMessageToNanoApp(message);
if (result != ContextHubTransaction.RESULT_SUCCESS) {
Assert.fail("Collect data from CHRE failed with result "
+ contextHubTransactionResultToString(result)
+ " while trying to send start message during "
+ getCurrentStepName() + " phase.");
}
}
/**
* @return The nanoapp message used to start the data collection in chre.
*/
private NanoAppMessage makeStepStartMessage(Step step) {
int messageType = ChreCrossValidationWifi.MessageType.STEP_START_VALUE;
ChreCrossValidationWifi.StepStartCommand stepStartCommand =
ChreCrossValidationWifi.StepStartCommand.newBuilder()
.setStep(step).build();
return NanoAppMessage.createMessageToNanoApp(
mNappBinary.getNanoAppId(), messageType, stepStartCommand.toByteArray());
}
/**
* Wait for a messaage from the nanoapp.
*/
private void waitForMessageFromNanoapp() {
try {
mAwaitDataLatch.await(AWAIT_STEP_RESULT_MESSAGE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Assert.fail("Interrupted while awaiting " + getCurrentStepName() + " step");
}
mAwaitDataLatch = new CountDownLatch(1);
Assert.assertTrue("Timed out while waiting for step result in " + getCurrentStepName()
+ " step", mDidReceiveNanoAppMessage.get());
mDidReceiveNanoAppMessage.set(false);
if (mErrorStr.get() != null) {
Assert.fail(mErrorStr.get());
}
}
/**
* @param capabilities The wifi capabilities message from CHRE.
* @return true if CHRE wifi has the necessary capabilities to run the test.
*/
private boolean chreWifiHasCapabilities(ChreCrossValidationWifi.WifiCapabilities capabilities) {
return (capabilities.getWifiCapabilities() & WIFI_CAPABILITIES_SCAN_MONITORING) != 0
&& (capabilities.getWifiCapabilities() & WIFI_CAPABILITIES_ON_DEMAND_SCAN) != 0;
}
private void waitForApScanResults() {
try {
mAwaitApWifiSetupScan.await(AWAIT_WIFI_SCAN_RESULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Assert.fail("Interrupted while awaiting ap wifi scan result");
}
Assert.assertTrue("AP wifi scan result failed asynchronously", mApWifiScanSuccess.get());
}
private void sendWifiScanResultsToChre() {
List<ScanResult> results = mWifiManager.getScanResults();
Assert.assertTrue("No wifi scan results returned from AP", !results.isEmpty());
for (int i = 0; i < results.size(); i++) {
sendMessageToNanoApp(makeWifiScanResultMessage(results.get(i), results.size(), i));
}
}
private NanoAppMessage makeWifiScanResultMessage(ScanResult result, int totalNumResults,
int resultIndex) {
int messageType = ChreCrossValidationWifi.MessageType.SCAN_RESULT_VALUE;
ChreCrossValidationWifi.WifiScanResult scanResult = ChreCrossValidationWifi.WifiScanResult
.newBuilder().setSsid(result.SSID)
.setBssid(ByteString.copyFrom(bssidToBytes(result.BSSID)))
.setTotalNumResults(totalNumResults).setResultIndex(resultIndex).build();
NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
mNappBinary.getNanoAppId(), messageType, scanResult.toByteArray());
return message;
}
@Override
protected void parseDataFromNanoAppMessage(NanoAppMessage message) {
mDidReceiveNanoAppMessage.set(true);
if (message.getMessageType()
== ChreCrossValidationWifi.MessageType.STEP_RESULT_VALUE) {
ChreTestCommon.TestResult testResult = null;
try {
testResult = ChreTestCommon.TestResult.parseFrom(message.getMessageBody());
} catch (InvalidProtocolBufferException e) {
setErrorStr("Error parsing protobuff: " + e);
mAwaitDataLatch.countDown();
return;
}
boolean success = getSuccessFromTestResult(testResult);
if (mStep.get() == Step.SETUP || mStep.get() == Step.VALIDATE) {
if (success) {
Log.i(TAG, getCurrentStepName() + " step success");
} else {
setErrorStr(getCurrentStepName() + " step failed: "
+ testResult.getErrorMessage().toStringUtf8());
}
} else {
setErrorStr("Received a step result message during step " + getCurrentStepName());
}
} else if (message.getMessageType()
== ChreCrossValidationWifi.MessageType.WIFI_CAPABILITIES_VALUE) {
if (mStep.get() != Step.CAPABILITIES) {
setErrorStr("Received a capabilities message during step " + getCurrentStepName());
}
ChreCrossValidationWifi.WifiCapabilities capabilities = null;
try {
capabilities = ChreCrossValidationWifi.WifiCapabilities.parseFrom(
message.getMessageBody());
} catch (InvalidProtocolBufferException e) {
setErrorStr("Error parsing protobuff: " + e);
mAwaitDataLatch.countDown();
return;
}
mWifiCapabilities.set(capabilities);
} else {
setErrorStr(String.format("Received message with unexpected type: %d",
message.getMessageType()));
}
// Each message should countdown the latch no matter success or fail
mAwaitDataLatch.countDown();
}
/**
* @return The boolean indicating test result success or failure from TestResult proto message.
*/
private boolean getSuccessFromTestResult(ChreTestCommon.TestResult testResult) {
return testResult.getCode() == ChreTestCommon.TestResult.Code.PASSED;
}
/**
* @return The string name of the current phase.
*/
private String getCurrentStepName() {
switch (mStep.get()) {
case INIT:
return "INIT";
case SETUP:
return "SETUP";
case VALIDATE:
return "VALIDATE";
default:
return "UNKNOWN";
}
}
private static byte[] bssidToBytes(String bssid) {
// the ScanResult.BSSID field comes in format ff:ff:ff:ff:ff and needs to be converted to
// bytes in order to be compared to CHRE bssid
return (new BigInteger(bssid.replace(":" , ""), 16)).toByteArray();
}
// TODO: Implement this method
@Override
protected void unregisterApDataListener() {}
}