1 package com.google.snippet; 2 3 import static java.nio.charset.StandardCharsets.UTF_8; 4 5 import android.content.Context; 6 import android.net.wifi.aware.DiscoverySession; 7 import android.net.wifi.aware.PeerHandle; 8 import android.net.wifi.aware.PublishConfig; 9 import android.net.wifi.aware.SubscribeConfig; 10 import android.net.wifi.aware.WifiAwareManager; 11 import android.net.wifi.aware.WifiAwareSession; 12 import android.os.Handler; 13 import android.os.HandlerThread; 14 import android.util.Log; 15 import android.util.Pair; 16 import androidx.test.platform.app.InstrumentationRegistry; 17 import com.google.android.mobly.snippet.Snippet; 18 import com.google.android.mobly.snippet.rpc.Rpc; 19 import com.google.common.collect.ImmutableSet; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.List; 23 24 /** An example snippet class with a simple Rpc. */ 25 public class WifiAwareSnippet implements Snippet { 26 27 private static class WifiAwareSnippetException extends Exception { 28 private static final long SERIAL_VERSION_UID = 1; 29 WifiAwareSnippetException(String msg)30 public WifiAwareSnippetException(String msg) { 31 super(msg); 32 } 33 WifiAwareSnippetException(String msg, Throwable err)34 public WifiAwareSnippetException(String msg, Throwable err) { 35 super(msg, err); 36 } 37 } 38 39 private static final String TAG = "WifiAwareSnippet"; 40 41 private static final String SERVICE_NAME = "CtsVerifierTestService"; 42 private static final byte[] MATCH_FILTER_BYTES = "bytes used for matching".getBytes(UTF_8); 43 private static final byte[] PUB_SSI = "Extra bytes in the publisher discovery".getBytes(UTF_8); 44 private static final byte[] SUB_SSI = 45 "Arbitrary bytes for the subscribe discovery".getBytes(UTF_8); 46 private static final int LARGE_ENOUGH_DISTANCE = 100000; // 100 meters 47 48 private final WifiAwareManager wifiAwareManager; 49 50 private final Context context; 51 52 private final HandlerThread handlerThread; 53 54 private final Handler handler; 55 56 private WifiAwareSession wifiAwareSession; 57 private DiscoverySession wifiAwareDiscoverySession; 58 private CallbackUtils.DiscoveryCb discoveryCb; 59 private PeerHandle peerHandle; 60 WifiAwareSnippet()61 public WifiAwareSnippet() { 62 context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 63 wifiAwareManager = context.getSystemService(WifiAwareManager.class); 64 handlerThread = new HandlerThread("Snippet-Aware"); 65 handlerThread.start(); 66 handler = new Handler(handlerThread.getLooper()); 67 } 68 69 @Rpc(description = "Execute attach.") attach()70 public void attach() throws InterruptedException, WifiAwareSnippetException { 71 CallbackUtils.AttachCb attachCb = new CallbackUtils.AttachCb(); 72 wifiAwareManager.attach(attachCb, handler); 73 Pair<CallbackUtils.AttachCb.CallbackCode, WifiAwareSession> results = attachCb.waitForAttach(); 74 if (results.first != CallbackUtils.AttachCb.CallbackCode.ON_ATTACHED) { 75 throw new WifiAwareSnippetException(String.format("executeTest: attach %s", results.first)); 76 } 77 wifiAwareSession = results.second; 78 if (wifiAwareSession == null) { 79 throw new WifiAwareSnippetException( 80 "executeTest: attach callback succeeded but null session returned!?"); 81 } 82 } 83 84 @Rpc(description = "Execute subscribe.") subscribe(Boolean isUnsolicited, Boolean isRangingRequired)85 public void subscribe(Boolean isUnsolicited, Boolean isRangingRequired) 86 throws InterruptedException, WifiAwareSnippetException { 87 discoveryCb = new CallbackUtils.DiscoveryCb(); 88 89 List<byte[]> matchFilter = new ArrayList<>(); 90 matchFilter.add(MATCH_FILTER_BYTES); 91 SubscribeConfig.Builder builder = 92 new SubscribeConfig.Builder() 93 .setServiceName(SERVICE_NAME) 94 .setServiceSpecificInfo(SUB_SSI) 95 .setMatchFilter(matchFilter) 96 .setSubscribeType( 97 isUnsolicited 98 ? SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE 99 : SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE) 100 .setTerminateNotificationEnabled(true); 101 102 if (isRangingRequired) { 103 // set up a distance that will always trigger - i.e. that we're already in that range 104 builder.setMaxDistanceMm(LARGE_ENOUGH_DISTANCE); 105 } 106 SubscribeConfig subscribeConfig = builder.build(); 107 Log.d(TAG, "executeTestSubscriber: subscribeConfig=" + subscribeConfig); 108 wifiAwareSession.subscribe(subscribeConfig, discoveryCb, handler); 109 110 // wait for results - subscribe session 111 CallbackUtils.DiscoveryCb.CallbackData callbackData = 112 discoveryCb.waitForCallbacks( 113 ImmutableSet.of( 114 CallbackUtils.DiscoveryCb.CallbackCode.ON_SUBSCRIBE_STARTED, 115 CallbackUtils.DiscoveryCb.CallbackCode.ON_SESSION_CONFIG_FAILED)); 116 if (callbackData.callbackCode != CallbackUtils.DiscoveryCb.CallbackCode.ON_SUBSCRIBE_STARTED) { 117 throw new WifiAwareSnippetException( 118 String.format("executeTestSubscriber: subscribe %s", callbackData.callbackCode)); 119 } 120 wifiAwareDiscoverySession = callbackData.subscribeDiscoverySession; 121 if (wifiAwareDiscoverySession == null) { 122 throw new WifiAwareSnippetException( 123 "executeTestSubscriber: subscribe succeeded but null session returned"); 124 } 125 Log.d(TAG, "executeTestSubscriber: subscribe succeeded"); 126 127 // 3. wait for discovery 128 callbackData = 129 discoveryCb.waitForCallbacks( 130 ImmutableSet.of( 131 isRangingRequired 132 ? CallbackUtils.DiscoveryCb.CallbackCode.ON_SERVICE_DISCOVERED_WITH_RANGE 133 : CallbackUtils.DiscoveryCb.CallbackCode.ON_SERVICE_DISCOVERED)); 134 135 if (callbackData.callbackCode == CallbackUtils.DiscoveryCb.CallbackCode.TIMEOUT) { 136 throw new WifiAwareSnippetException("executeTestSubscriber: waiting for discovery TIMEOUT"); 137 } 138 peerHandle = callbackData.peerHandle; 139 if (!isRangingRequired) { 140 Log.d(TAG, "executeTestSubscriber: discovery"); 141 } else { 142 Log.d(TAG, "executeTestSubscriber: discovery with range=" + callbackData.distanceMm); 143 } 144 145 if (!Arrays.equals(PUB_SSI, callbackData.serviceSpecificInfo)) { 146 throw new WifiAwareSnippetException( 147 "executeTestSubscriber: discovery but SSI mismatch: rx='" 148 + new String(callbackData.serviceSpecificInfo, UTF_8) 149 + "'"); 150 } 151 if (callbackData.matchFilter.size() != 1 152 || !Arrays.equals(MATCH_FILTER_BYTES, callbackData.matchFilter.get(0))) { 153 StringBuilder sb = new StringBuilder(); 154 sb.append("size=").append(callbackData.matchFilter.size()); 155 for (byte[] mf : callbackData.matchFilter) { 156 sb.append(", e='").append(new String(mf, UTF_8)).append("'"); 157 } 158 throw new WifiAwareSnippetException( 159 "executeTestSubscriber: discovery but matchFilter mismatch: " + sb); 160 } 161 if (peerHandle == null) { 162 throw new WifiAwareSnippetException("executeTestSubscriber: discovery but null peerHandle"); 163 } 164 } 165 166 @Rpc(description = "Send message.") sendMessage(int messageId, String message)167 public void sendMessage(int messageId, String message) 168 throws InterruptedException, WifiAwareSnippetException { 169 // 4. send message & wait for send status 170 wifiAwareDiscoverySession.sendMessage(peerHandle, messageId, message.getBytes(UTF_8)); 171 CallbackUtils.DiscoveryCb.CallbackData callbackData = 172 discoveryCb.waitForCallbacks( 173 ImmutableSet.of( 174 CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_SUCCEEDED, 175 CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_FAILED)); 176 177 if (callbackData.callbackCode 178 != CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_SUCCEEDED) { 179 throw new WifiAwareSnippetException( 180 String.format("executeTestSubscriber: sendMessage %s", callbackData.callbackCode)); 181 } 182 Log.d(TAG, "executeTestSubscriber: send message succeeded"); 183 if (callbackData.messageId != messageId) { 184 throw new WifiAwareSnippetException( 185 "executeTestSubscriber: send message message ID mismatch: " + callbackData.messageId); 186 } 187 } 188 189 @Rpc(description = "Create publish session.") publish(Boolean isUnsolicited, Boolean isRangingRequired)190 public void publish(Boolean isUnsolicited, Boolean isRangingRequired) 191 throws WifiAwareSnippetException, InterruptedException { 192 discoveryCb = new CallbackUtils.DiscoveryCb(); 193 194 // 2. publish 195 List<byte[]> matchFilter = new ArrayList<>(); 196 matchFilter.add(MATCH_FILTER_BYTES); 197 PublishConfig publishConfig = 198 new PublishConfig.Builder() 199 .setServiceName(SERVICE_NAME) 200 .setServiceSpecificInfo(PUB_SSI) 201 .setMatchFilter(matchFilter) 202 .setPublishType( 203 isUnsolicited 204 ? PublishConfig.PUBLISH_TYPE_UNSOLICITED 205 : PublishConfig.PUBLISH_TYPE_SOLICITED) 206 .setTerminateNotificationEnabled(true) 207 .setRangingEnabled(isRangingRequired) 208 .build(); 209 Log.d(TAG, "executeTestPublisher: publishConfig=" + publishConfig); 210 wifiAwareSession.publish(publishConfig, discoveryCb, handler); 211 212 // wait for results - publish session 213 CallbackUtils.DiscoveryCb.CallbackData callbackData = 214 discoveryCb.waitForCallbacks( 215 ImmutableSet.of( 216 CallbackUtils.DiscoveryCb.CallbackCode.ON_PUBLISH_STARTED, 217 CallbackUtils.DiscoveryCb.CallbackCode.ON_SESSION_CONFIG_FAILED)); 218 if (callbackData.callbackCode != CallbackUtils.DiscoveryCb.CallbackCode.ON_PUBLISH_STARTED) { 219 throw new WifiAwareSnippetException( 220 String.format("executeTestPublisher: publish %s", callbackData.callbackCode)); 221 } 222 wifiAwareDiscoverySession = callbackData.publishDiscoverySession; 223 if (wifiAwareDiscoverySession == null) { 224 throw new WifiAwareSnippetException( 225 "executeTestPublisher: publish succeeded but null session returned"); 226 } 227 Log.d(TAG, "executeTestPublisher: publish succeeded"); 228 } 229 230 @Rpc(description = "Receive message.") receiveMessage()231 public String receiveMessage() throws WifiAwareSnippetException, InterruptedException { 232 // 3. wait to receive message. 233 CallbackUtils.DiscoveryCb.CallbackData callbackData = 234 discoveryCb.waitForCallbacks( 235 ImmutableSet.of(CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_RECEIVED)); 236 peerHandle = callbackData.peerHandle; 237 Log.d(TAG, "executeTestPublisher: received message"); 238 239 if (peerHandle == null) { 240 throw new WifiAwareSnippetException( 241 "executeTestPublisher: received message but peerHandle is null!?"); 242 } 243 return new String(callbackData.serviceSpecificInfo, UTF_8); 244 } 245 246 @Override shutdown()247 public void shutdown() { 248 if (wifiAwareDiscoverySession != null) { 249 wifiAwareDiscoverySession.close(); 250 wifiAwareDiscoverySession = null; 251 } 252 if (wifiAwareSession != null) { 253 wifiAwareSession.close(); 254 wifiAwareSession = null; 255 } 256 } 257 } 258