1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.snippet; 18 19 import static java.util.concurrent.TimeUnit.SECONDS; 20 21 import android.net.ConnectivityManager; 22 import android.net.Network; 23 import android.net.NetworkCapabilities; 24 import android.net.wifi.aware.AttachCallback; 25 import android.net.wifi.aware.DiscoverySessionCallback; 26 import android.net.wifi.aware.IdentityChangedListener; 27 import android.net.wifi.aware.PeerHandle; 28 import android.net.wifi.aware.PublishDiscoverySession; 29 import android.net.wifi.aware.SubscribeDiscoverySession; 30 import android.net.wifi.aware.WifiAwareSession; 31 import android.net.wifi.rtt.RangingResult; 32 import android.net.wifi.rtt.RangingResultCallback; 33 import android.util.Log; 34 import android.util.Pair; 35 import com.google.common.collect.ImmutableSet; 36 import java.util.ArrayDeque; 37 import java.util.List; 38 import java.util.Set; 39 import java.util.concurrent.CountDownLatch; 40 41 /** Blocking callbacks for Wi-Fi Aware and Connectivity Manager. */ 42 public final class CallbackUtils { 43 private static final String TAG = "CallbackUtils"; 44 45 public static final int CALLBACK_TIMEOUT_SEC = 15; 46 47 /** 48 * Utility AttachCallback - provides mechanism to block execution with the waitForAttach method. 49 */ 50 public static class AttachCb extends AttachCallback { 51 52 /** Callback codes. */ 53 public static enum CallbackCode { 54 TIMEOUT, 55 ON_ATTACHED, 56 ON_ATTACH_FAILED 57 }; 58 59 private final CountDownLatch blocker = new CountDownLatch(1); 60 private CallbackCode callbackCode = CallbackCode.TIMEOUT; 61 private WifiAwareSession wifiAwareSession = null; 62 63 @Override onAttached(WifiAwareSession session)64 public void onAttached(WifiAwareSession session) { 65 callbackCode = CallbackCode.ON_ATTACHED; 66 wifiAwareSession = session; 67 blocker.countDown(); 68 } 69 70 @Override onAttachFailed()71 public void onAttachFailed() { 72 callbackCode = CallbackCode.ON_ATTACH_FAILED; 73 blocker.countDown(); 74 } 75 76 /** 77 * Wait (blocks) for any AttachCallback callback or timeout. 78 * 79 * @return A pair of values: the callback constant (or TIMEOUT) and the WifiAwareSession created 80 * when attach successful - null otherwise (attach failure or timeout). 81 */ waitForAttach()82 public Pair<CallbackCode, WifiAwareSession> waitForAttach() throws InterruptedException { 83 if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) { 84 return new Pair<>(callbackCode, wifiAwareSession); 85 } 86 87 return new Pair<>(CallbackCode.TIMEOUT, null); 88 } 89 } 90 91 /** 92 * Utility IdentityChangedListener - provides mechanism to block execution with the 93 * waitForIdentity method. Single shot listener - only listens for the first triggered callback. 94 */ 95 public static class IdentityListenerSingleShot extends IdentityChangedListener { 96 private final CountDownLatch blocker = new CountDownLatch(1); 97 private byte[] mac = null; 98 99 @Override onIdentityChanged(byte[] mac)100 public void onIdentityChanged(byte[] mac) { 101 if (this.mac != null) { 102 return; 103 } 104 105 this.mac = mac; 106 blocker.countDown(); 107 } 108 109 /** 110 * Wait (blocks) for the onIdentityChanged callback or a timeout. 111 * 112 * @return The MAC address returned by the onIdentityChanged() callback, or null on timeout. 113 */ waitForMac()114 public byte[] waitForMac() throws InterruptedException { 115 if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) { 116 return mac; 117 } 118 119 return null; 120 } 121 } 122 123 /** 124 * Utility NetworkCallback - provides mechanism for blocking/serializing access with the 125 * waitForNetwork method. 126 */ 127 public static class NetworkCb extends ConnectivityManager.NetworkCallback { 128 private final CountDownLatch blocker = new CountDownLatch(1); 129 private Network network = null; 130 private NetworkCapabilities networkCapabilities = null; 131 132 @Override onUnavailable()133 public void onUnavailable() { 134 networkCapabilities = null; 135 blocker.countDown(); 136 } 137 138 @Override onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities)139 public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { 140 this.network = network; 141 this.networkCapabilities = networkCapabilities; 142 blocker.countDown(); 143 } 144 145 /** 146 * Wait (blocks) for Capabilities Changed callback - or timesout. 147 * 148 * @return Network + NetworkCapabilities (pair) if occurred, null otherwise. 149 */ waitForNetworkCapabilities()150 public Pair<Network, NetworkCapabilities> waitForNetworkCapabilities() 151 throws InterruptedException { 152 if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) { 153 return Pair.create(network, networkCapabilities); 154 } 155 return null; 156 } 157 } 158 159 /** 160 * Utility DiscoverySessionCallback - provides mechanism to block/serialize Aware discovery 161 * operations using the waitForCallbacks() method. 162 */ 163 public static class DiscoveryCb extends DiscoverySessionCallback { 164 /** Callback codes. */ 165 public static enum CallbackCode { 166 TIMEOUT, 167 ON_PUBLISH_STARTED, 168 ON_SUBSCRIBE_STARTED, 169 ON_SESSION_CONFIG_UPDATED, 170 ON_SESSION_CONFIG_FAILED, 171 ON_SESSION_TERMINATED, 172 ON_SERVICE_DISCOVERED, 173 ON_MESSAGE_SEND_SUCCEEDED, 174 ON_MESSAGE_SEND_FAILED, 175 ON_MESSAGE_RECEIVED, 176 ON_SERVICE_DISCOVERED_WITH_RANGE, 177 }; 178 179 /** 180 * Data container for all parameters which can be returned by any DiscoverySessionCallback 181 * callback. 182 */ 183 public static class CallbackData { CallbackData(CallbackCode callbackCode)184 public CallbackData(CallbackCode callbackCode) { 185 this.callbackCode = callbackCode; 186 } 187 188 public CallbackCode callbackCode; 189 190 public PublishDiscoverySession publishDiscoverySession; 191 public SubscribeDiscoverySession subscribeDiscoverySession; 192 public PeerHandle peerHandle; 193 public byte[] serviceSpecificInfo; 194 public List<byte[]> matchFilter; 195 public int messageId; 196 public int distanceMm; 197 } 198 199 private CountDownLatch blocker = null; 200 private Set<CallbackCode> waitForCallbackCodes = ImmutableSet.of(); 201 202 private final Object lock = new Object(); 203 private final ArrayDeque<CallbackData> callbackQueue = new ArrayDeque<>(); 204 processCallback(CallbackData callbackData)205 private void processCallback(CallbackData callbackData) { 206 synchronized (lock) { 207 callbackQueue.addLast(callbackData); 208 if (blocker != null && waitForCallbackCodes.contains(callbackData.callbackCode)) { 209 blocker.countDown(); 210 } 211 } 212 } 213 getAndRemoveFirst(Set<CallbackCode> callbackCodes)214 private CallbackData getAndRemoveFirst(Set<CallbackCode> callbackCodes) { 215 synchronized (lock) { 216 for (CallbackData cbd : callbackQueue) { 217 if (callbackCodes.contains(cbd.callbackCode)) { 218 callbackQueue.remove(cbd); 219 return cbd; 220 } 221 } 222 } 223 224 return null; 225 } 226 waitForCallbacks(Set<CallbackCode> callbackCodes, boolean timeout)227 private CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes, boolean timeout) 228 throws InterruptedException { 229 synchronized (lock) { 230 CallbackData cbd = getAndRemoveFirst(callbackCodes); 231 if (cbd != null) { 232 return cbd; 233 } 234 235 waitForCallbackCodes = callbackCodes; 236 blocker = new CountDownLatch(1); 237 } 238 239 boolean finishedNormally = true; 240 if (timeout) { 241 finishedNormally = blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS); 242 } else { 243 blocker.await(); 244 } 245 if (finishedNormally) { 246 CallbackData cbd = getAndRemoveFirst(callbackCodes); 247 if (cbd != null) { 248 return cbd; 249 } 250 251 Log.wtf( 252 TAG, 253 "DiscoveryCb.waitForCallback: callbackCodes=" 254 + callbackCodes 255 + ": did not time-out but doesn't have any of the requested callbacks in " 256 + "the stack!?"); 257 // falling-through to TIMEOUT 258 } 259 260 return new CallbackData(CallbackCode.TIMEOUT); 261 } 262 263 /** 264 * Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the 265 * CallbackData structure whose CallbackData.callback specifies the callback which was 266 * triggered. The callback may be TIMEOUT. 267 * 268 * <p>Note: other callbacks happening while while waiting for the specified callback(s) will be 269 * queued. 270 */ waitForCallbacks(Set<CallbackCode> callbackCodes)271 public CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes) 272 throws InterruptedException { 273 return waitForCallbacks(callbackCodes, true); 274 } 275 276 /** 277 * Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the 278 * CallbackData structure whose CallbackData.callback specifies the callback which was 279 * triggered. 280 * 281 * <p>This call will not timeout - it can be interrupted though (which results in a thrown 282 * exception). 283 * 284 * <p>Note: other callbacks happening while while waiting for the specified callback(s) will be 285 * queued. 286 */ waitForCallbacksNoTimeout(Set<CallbackCode> callbackCodes)287 public CallbackData waitForCallbacksNoTimeout(Set<CallbackCode> callbackCodes) 288 throws InterruptedException { 289 return waitForCallbacks(callbackCodes, false); 290 } 291 292 @Override onPublishStarted(PublishDiscoverySession session)293 public void onPublishStarted(PublishDiscoverySession session) { 294 CallbackData callbackData = new CallbackData(CallbackCode.ON_PUBLISH_STARTED); 295 callbackData.publishDiscoverySession = session; 296 processCallback(callbackData); 297 } 298 299 @Override onSubscribeStarted(SubscribeDiscoverySession session)300 public void onSubscribeStarted(SubscribeDiscoverySession session) { 301 CallbackData callbackData = new CallbackData(CallbackCode.ON_SUBSCRIBE_STARTED); 302 callbackData.subscribeDiscoverySession = session; 303 processCallback(callbackData); 304 } 305 306 @Override onSessionConfigUpdated()307 public void onSessionConfigUpdated() { 308 CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_UPDATED); 309 processCallback(callbackData); 310 } 311 312 @Override onSessionConfigFailed()313 public void onSessionConfigFailed() { 314 CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_FAILED); 315 processCallback(callbackData); 316 } 317 318 @Override onSessionTerminated()319 public void onSessionTerminated() { 320 CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_TERMINATED); 321 processCallback(callbackData); 322 } 323 324 @Override onServiceDiscovered( PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter)325 public void onServiceDiscovered( 326 PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter) { 327 CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED); 328 callbackData.peerHandle = peerHandle; 329 callbackData.serviceSpecificInfo = serviceSpecificInfo; 330 callbackData.matchFilter = matchFilter; 331 processCallback(callbackData); 332 } 333 334 @Override onServiceDiscoveredWithinRange( PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm)335 public void onServiceDiscoveredWithinRange( 336 PeerHandle peerHandle, 337 byte[] serviceSpecificInfo, 338 List<byte[]> matchFilter, 339 int distanceMm) { 340 CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED_WITH_RANGE); 341 callbackData.peerHandle = peerHandle; 342 callbackData.serviceSpecificInfo = serviceSpecificInfo; 343 callbackData.matchFilter = matchFilter; 344 callbackData.distanceMm = distanceMm; 345 processCallback(callbackData); 346 } 347 348 @Override onMessageSendSucceeded(int messageId)349 public void onMessageSendSucceeded(int messageId) { 350 CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_SUCCEEDED); 351 callbackData.messageId = messageId; 352 processCallback(callbackData); 353 } 354 355 @Override onMessageSendFailed(int messageId)356 public void onMessageSendFailed(int messageId) { 357 CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_FAILED); 358 callbackData.messageId = messageId; 359 processCallback(callbackData); 360 } 361 362 @Override onMessageReceived(PeerHandle peerHandle, byte[] message)363 public void onMessageReceived(PeerHandle peerHandle, byte[] message) { 364 CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_RECEIVED); 365 callbackData.peerHandle = peerHandle; 366 callbackData.serviceSpecificInfo = message; 367 processCallback(callbackData); 368 } 369 } 370 371 /** 372 * Utility RangingResultCallback - provides mechanism for blocking/serializing access with the 373 * waitForRangingResults method. 374 */ 375 public static class RangingCb extends RangingResultCallback { 376 public static final int TIMEOUT = -1; 377 public static final int ON_FAILURE = 0; 378 public static final int ON_RESULTS = 1; 379 380 private final CountDownLatch blocker = new CountDownLatch(1); 381 private int status = TIMEOUT; 382 private List<RangingResult> results = null; 383 384 /** 385 * Wait (blocks) for Ranging results callbacks - or times-out. 386 * 387 * @return Pair of status & Ranging results if succeeded, null otherwise. 388 */ waitForRangingResults()389 public Pair<Integer, List<RangingResult>> waitForRangingResults() throws InterruptedException { 390 if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) { 391 return new Pair<>(status, results); 392 } 393 return new Pair<>(TIMEOUT, null); 394 } 395 396 @Override onRangingFailure(int code)397 public void onRangingFailure(int code) { 398 status = ON_FAILURE; 399 blocker.countDown(); 400 } 401 402 @Override onRangingResults(List<RangingResult> results)403 public void onRangingResults(List<RangingResult> results) { 404 status = ON_RESULTS; 405 this.results = results; 406 blocker.countDown(); 407 } 408 } 409 CallbackUtils()410 private CallbackUtils() {} 411 } 412