• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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