blob: a23d6191d926809662132a7ca5b75d88703f548d [file] [log] [blame]
/*
* Copyright (C) 2017 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.snippet;
import static java.util.concurrent.TimeUnit.SECONDS;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.aware.AttachCallback;
import android.net.wifi.aware.DiscoverySessionCallback;
import android.net.wifi.aware.IdentityChangedListener;
import android.net.wifi.aware.PeerHandle;
import android.net.wifi.aware.PublishDiscoverySession;
import android.net.wifi.aware.SubscribeDiscoverySession;
import android.net.wifi.aware.WifiAwareSession;
import android.net.wifi.rtt.RangingResult;
import android.net.wifi.rtt.RangingResultCallback;
import android.util.Log;
import android.util.Pair;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
/** Blocking callbacks for Wi-Fi Aware and Connectivity Manager. */
public final class CallbackUtils {
private static final String TAG = "CallbackUtils";
public static final int CALLBACK_TIMEOUT_SEC = 15;
/**
* Utility AttachCallback - provides mechanism to block execution with the waitForAttach method.
*/
public static class AttachCb extends AttachCallback {
/** Callback codes. */
public static enum CallbackCode {
TIMEOUT,
ON_ATTACHED,
ON_ATTACH_FAILED
};
private final CountDownLatch blocker = new CountDownLatch(1);
private CallbackCode callbackCode = CallbackCode.TIMEOUT;
private WifiAwareSession wifiAwareSession = null;
@Override
public void onAttached(WifiAwareSession session) {
callbackCode = CallbackCode.ON_ATTACHED;
wifiAwareSession = session;
blocker.countDown();
}
@Override
public void onAttachFailed() {
callbackCode = CallbackCode.ON_ATTACH_FAILED;
blocker.countDown();
}
/**
* Wait (blocks) for any AttachCallback callback or timeout.
*
* @return A pair of values: the callback constant (or TIMEOUT) and the WifiAwareSession created
* when attach successful - null otherwise (attach failure or timeout).
*/
public Pair<CallbackCode, WifiAwareSession> waitForAttach() throws InterruptedException {
if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
return new Pair<>(callbackCode, wifiAwareSession);
}
return new Pair<>(CallbackCode.TIMEOUT, null);
}
}
/**
* Utility IdentityChangedListener - provides mechanism to block execution with the
* waitForIdentity method. Single shot listener - only listens for the first triggered callback.
*/
public static class IdentityListenerSingleShot extends IdentityChangedListener {
private final CountDownLatch blocker = new CountDownLatch(1);
private byte[] mac = null;
@Override
public void onIdentityChanged(byte[] mac) {
if (this.mac != null) {
return;
}
this.mac = mac;
blocker.countDown();
}
/**
* Wait (blocks) for the onIdentityChanged callback or a timeout.
*
* @return The MAC address returned by the onIdentityChanged() callback, or null on timeout.
*/
public byte[] waitForMac() throws InterruptedException {
if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
return mac;
}
return null;
}
}
/**
* Utility NetworkCallback - provides mechanism for blocking/serializing access with the
* waitForNetwork method.
*/
public static class NetworkCb extends ConnectivityManager.NetworkCallback {
private final CountDownLatch blocker = new CountDownLatch(1);
private Network network = null;
private NetworkCapabilities networkCapabilities = null;
@Override
public void onUnavailable() {
networkCapabilities = null;
blocker.countDown();
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
this.network = network;
this.networkCapabilities = networkCapabilities;
blocker.countDown();
}
/**
* Wait (blocks) for Capabilities Changed callback - or timesout.
*
* @return Network + NetworkCapabilities (pair) if occurred, null otherwise.
*/
public Pair<Network, NetworkCapabilities> waitForNetworkCapabilities()
throws InterruptedException {
if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
return Pair.create(network, networkCapabilities);
}
return null;
}
}
/**
* Utility DiscoverySessionCallback - provides mechanism to block/serialize Aware discovery
* operations using the waitForCallbacks() method.
*/
public static class DiscoveryCb extends DiscoverySessionCallback {
/** Callback codes. */
public static enum CallbackCode {
TIMEOUT,
ON_PUBLISH_STARTED,
ON_SUBSCRIBE_STARTED,
ON_SESSION_CONFIG_UPDATED,
ON_SESSION_CONFIG_FAILED,
ON_SESSION_TERMINATED,
ON_SERVICE_DISCOVERED,
ON_MESSAGE_SEND_SUCCEEDED,
ON_MESSAGE_SEND_FAILED,
ON_MESSAGE_RECEIVED,
ON_SERVICE_DISCOVERED_WITH_RANGE,
};
/**
* Data container for all parameters which can be returned by any DiscoverySessionCallback
* callback.
*/
public static class CallbackData {
public CallbackData(CallbackCode callbackCode) {
this.callbackCode = callbackCode;
}
public CallbackCode callbackCode;
public PublishDiscoverySession publishDiscoverySession;
public SubscribeDiscoverySession subscribeDiscoverySession;
public PeerHandle peerHandle;
public byte[] serviceSpecificInfo;
public List<byte[]> matchFilter;
public int messageId;
public int distanceMm;
}
private CountDownLatch blocker = null;
private Set<CallbackCode> waitForCallbackCodes = ImmutableSet.of();
private final Object lock = new Object();
private final ArrayDeque<CallbackData> callbackQueue = new ArrayDeque<>();
private void processCallback(CallbackData callbackData) {
synchronized (lock) {
callbackQueue.addLast(callbackData);
if (blocker != null && waitForCallbackCodes.contains(callbackData.callbackCode)) {
blocker.countDown();
}
}
}
private CallbackData getAndRemoveFirst(Set<CallbackCode> callbackCodes) {
synchronized (lock) {
for (CallbackData cbd : callbackQueue) {
if (callbackCodes.contains(cbd.callbackCode)) {
callbackQueue.remove(cbd);
return cbd;
}
}
}
return null;
}
private CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes, boolean timeout)
throws InterruptedException {
synchronized (lock) {
CallbackData cbd = getAndRemoveFirst(callbackCodes);
if (cbd != null) {
return cbd;
}
waitForCallbackCodes = callbackCodes;
blocker = new CountDownLatch(1);
}
boolean finishedNormally = true;
if (timeout) {
finishedNormally = blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS);
} else {
blocker.await();
}
if (finishedNormally) {
CallbackData cbd = getAndRemoveFirst(callbackCodes);
if (cbd != null) {
return cbd;
}
Log.wtf(
TAG,
"DiscoveryCb.waitForCallback: callbackCodes="
+ callbackCodes
+ ": did not time-out but doesn't have any of the requested callbacks in "
+ "the stack!?");
// falling-through to TIMEOUT
}
return new CallbackData(CallbackCode.TIMEOUT);
}
/**
* Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the
* CallbackData structure whose CallbackData.callback specifies the callback which was
* triggered. The callback may be TIMEOUT.
*
* <p>Note: other callbacks happening while while waiting for the specified callback(s) will be
* queued.
*/
public CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes)
throws InterruptedException {
return waitForCallbacks(callbackCodes, true);
}
/**
* Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the
* CallbackData structure whose CallbackData.callback specifies the callback which was
* triggered.
*
* <p>This call will not timeout - it can be interrupted though (which results in a thrown
* exception).
*
* <p>Note: other callbacks happening while while waiting for the specified callback(s) will be
* queued.
*/
public CallbackData waitForCallbacksNoTimeout(Set<CallbackCode> callbackCodes)
throws InterruptedException {
return waitForCallbacks(callbackCodes, false);
}
@Override
public void onPublishStarted(PublishDiscoverySession session) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_PUBLISH_STARTED);
callbackData.publishDiscoverySession = session;
processCallback(callbackData);
}
@Override
public void onSubscribeStarted(SubscribeDiscoverySession session) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SUBSCRIBE_STARTED);
callbackData.subscribeDiscoverySession = session;
processCallback(callbackData);
}
@Override
public void onSessionConfigUpdated() {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_UPDATED);
processCallback(callbackData);
}
@Override
public void onSessionConfigFailed() {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_FAILED);
processCallback(callbackData);
}
@Override
public void onSessionTerminated() {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_TERMINATED);
processCallback(callbackData);
}
@Override
public void onServiceDiscovered(
PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED);
callbackData.peerHandle = peerHandle;
callbackData.serviceSpecificInfo = serviceSpecificInfo;
callbackData.matchFilter = matchFilter;
processCallback(callbackData);
}
@Override
public void onServiceDiscoveredWithinRange(
PeerHandle peerHandle,
byte[] serviceSpecificInfo,
List<byte[]> matchFilter,
int distanceMm) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED_WITH_RANGE);
callbackData.peerHandle = peerHandle;
callbackData.serviceSpecificInfo = serviceSpecificInfo;
callbackData.matchFilter = matchFilter;
callbackData.distanceMm = distanceMm;
processCallback(callbackData);
}
@Override
public void onMessageSendSucceeded(int messageId) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_SUCCEEDED);
callbackData.messageId = messageId;
processCallback(callbackData);
}
@Override
public void onMessageSendFailed(int messageId) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_FAILED);
callbackData.messageId = messageId;
processCallback(callbackData);
}
@Override
public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_RECEIVED);
callbackData.peerHandle = peerHandle;
callbackData.serviceSpecificInfo = message;
processCallback(callbackData);
}
}
/**
* Utility RangingResultCallback - provides mechanism for blocking/serializing access with the
* waitForRangingResults method.
*/
public static class RangingCb extends RangingResultCallback {
public static final int TIMEOUT = -1;
public static final int ON_FAILURE = 0;
public static final int ON_RESULTS = 1;
private final CountDownLatch blocker = new CountDownLatch(1);
private int status = TIMEOUT;
private List<RangingResult> results = null;
/**
* Wait (blocks) for Ranging results callbacks - or times-out.
*
* @return Pair of status & Ranging results if succeeded, null otherwise.
*/
public Pair<Integer, List<RangingResult>> waitForRangingResults() throws InterruptedException {
if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
return new Pair<>(status, results);
}
return new Pair<>(TIMEOUT, null);
}
@Override
public void onRangingFailure(int code) {
status = ON_FAILURE;
blocker.countDown();
}
@Override
public void onRangingResults(List<RangingResult> results) {
status = ON_RESULTS;
this.results = results;
blocker.countDown();
}
}
private CallbackUtils() {}
}