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.googlecode.android_scripting.facade.wifi;
18 
19 import android.app.Service;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.content.pm.PackageManager;
25 import android.net.NetworkSpecifier;
26 import android.net.wifi.RttManager;
27 import android.net.wifi.RttManager.RttResult;
28 import android.net.wifi.aware.AttachCallback;
29 import android.net.wifi.aware.ConfigRequest;
30 import android.net.wifi.aware.DiscoverySession;
31 import android.net.wifi.aware.DiscoverySessionCallback;
32 import android.net.wifi.aware.IdentityChangedListener;
33 import android.net.wifi.aware.PeerHandle;
34 import android.net.wifi.aware.PublishConfig;
35 import android.net.wifi.aware.PublishDiscoverySession;
36 import android.net.wifi.aware.SubscribeConfig;
37 import android.net.wifi.aware.SubscribeDiscoverySession;
38 import android.net.wifi.aware.TlvBufferUtils;
39 import android.net.wifi.aware.WifiAwareManager;
40 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
41 import android.net.wifi.aware.WifiAwareSession;
42 import android.os.Bundle;
43 import android.os.Parcelable;
44 import android.os.RemoteException;
45 import android.text.TextUtils;
46 import android.util.Base64;
47 import android.util.SparseArray;
48 
49 import com.android.internal.annotations.GuardedBy;
50 
51 import libcore.util.HexEncoding;
52 
53 import com.googlecode.android_scripting.facade.EventFacade;
54 import com.googlecode.android_scripting.facade.FacadeManager;
55 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
56 import com.googlecode.android_scripting.rpc.Rpc;
57 import com.googlecode.android_scripting.rpc.RpcOptional;
58 import com.googlecode.android_scripting.rpc.RpcParameter;
59 
60 import org.json.JSONArray;
61 import org.json.JSONException;
62 import org.json.JSONObject;
63 
64 import java.nio.charset.StandardCharsets;
65 import java.util.ArrayList;
66 import java.util.List;
67 
68 /**
69  * WifiAwareManager functions.
70  */
71 public class WifiAwareManagerFacade extends RpcReceiver {
72     private final Service mService;
73     private final EventFacade mEventFacade;
74     private final WifiAwareStateChangedReceiver mStateChangedReceiver;
75 
76     private final Object mLock = new Object(); // lock access to the following vars
77 
78     @GuardedBy("mLock")
79     private WifiAwareManager mMgr;
80 
81     @GuardedBy("mLock")
82     private int mNextDiscoverySessionId = 1;
83     @GuardedBy("mLock")
84     private SparseArray<DiscoverySession> mDiscoverySessions = new SparseArray<>();
getNextDiscoverySessionId()85     private int getNextDiscoverySessionId() {
86         synchronized (mLock) {
87             return mNextDiscoverySessionId++;
88         }
89     }
90 
91     @GuardedBy("mLock")
92     private int mNextSessionId = 1;
93     @GuardedBy("mLock")
94     private SparseArray<WifiAwareSession> mSessions = new SparseArray<>();
getNextSessionId()95     private int getNextSessionId() {
96         synchronized (mLock) {
97             return mNextSessionId++;
98         }
99     }
100 
101     @GuardedBy("mLock")
102     private SparseArray<Long> mMessageStartTime = new SparseArray<>();
103 
104     private static final String NS_KEY_TYPE = "type";
105     private static final String NS_KEY_ROLE = "role";
106     private static final String NS_KEY_CLIENT_ID = "client_id";
107     private static final String NS_KEY_SESSION_ID = "session_id";
108     private static final String NS_KEY_PEER_ID = "peer_id";
109     private static final String NS_KEY_PEER_MAC = "peer_mac";
110     private static final String NS_KEY_PMK = "pmk";
111     private static final String NS_KEY_PASSPHRASE = "passphrase";
112     private static final String NS_KEY_PORT = "port";
113     private static final String NS_KEY_TRANSPORT_PROTOCOL = "transport_protocol";
114 
getJsonString(WifiAwareNetworkSpecifier ns)115     private static String getJsonString(WifiAwareNetworkSpecifier ns) throws JSONException {
116         JSONObject j = new JSONObject();
117 
118         j.put(NS_KEY_TYPE, ns.type);
119         j.put(NS_KEY_ROLE, ns.role);
120         j.put(NS_KEY_CLIENT_ID, ns.clientId);
121         j.put(NS_KEY_SESSION_ID, ns.sessionId);
122         j.put(NS_KEY_PEER_ID, ns.peerId);
123         if (ns.peerMac != null) {
124             j.put(NS_KEY_PEER_MAC, Base64.encodeToString(ns.peerMac, Base64.DEFAULT));
125         }
126         if (ns.pmk != null) {
127             j.put(NS_KEY_PMK, Base64.encodeToString(ns.pmk, Base64.DEFAULT));
128         }
129         if (ns.passphrase != null) {
130             j.put(NS_KEY_PASSPHRASE, ns.passphrase);
131         }
132         if (ns.port != 0) {
133             j.put(NS_KEY_PORT, ns.port);
134         }
135         if (ns.transportProtocol != -1) {
136             j.put(NS_KEY_TRANSPORT_PROTOCOL, ns.transportProtocol);
137         }
138 
139         return j.toString();
140     }
141 
getNetworkSpecifier(JSONObject j)142     public static NetworkSpecifier getNetworkSpecifier(JSONObject j) throws JSONException {
143         if (j == null) {
144             return null;
145         }
146 
147         int type = 0, role = 0, clientId = 0, sessionId = 0, peerId = 0;
148         byte[] peerMac = null;
149         byte[] pmk = null;
150         String passphrase = null;
151         int port = 0, transportProtocol = -1;
152 
153         if (j.has(NS_KEY_TYPE)) {
154             type = j.getInt((NS_KEY_TYPE));
155         }
156         if (j.has(NS_KEY_ROLE)) {
157             role = j.getInt((NS_KEY_ROLE));
158         }
159         if (j.has(NS_KEY_CLIENT_ID)) {
160             clientId = j.getInt((NS_KEY_CLIENT_ID));
161         }
162         if (j.has(NS_KEY_SESSION_ID)) {
163             sessionId = j.getInt((NS_KEY_SESSION_ID));
164         }
165         if (j.has(NS_KEY_PEER_ID)) {
166             peerId = j.getInt((NS_KEY_PEER_ID));
167         }
168         if (j.has(NS_KEY_PEER_MAC)) {
169             peerMac = Base64.decode(j.getString(NS_KEY_PEER_MAC), Base64.DEFAULT);
170         }
171         if (j.has(NS_KEY_PMK)) {
172             pmk = Base64.decode(j.getString(NS_KEY_PMK), Base64.DEFAULT);
173         }
174         if (j.has(NS_KEY_PASSPHRASE)) {
175             passphrase = j.getString(NS_KEY_PASSPHRASE);
176         }
177         if (j.has(NS_KEY_PORT)) {
178             port = j.getInt(NS_KEY_PORT);
179         }
180         if (j.has(NS_KEY_TRANSPORT_PROTOCOL)) {
181             transportProtocol = j.getInt(NS_KEY_TRANSPORT_PROTOCOL);
182         }
183 
184         return new WifiAwareNetworkSpecifier(type, role, clientId, sessionId, peerId, peerMac, pmk,
185                 passphrase, port, transportProtocol);
186     }
187 
getStringOrNull(JSONObject j, String name)188     private static String getStringOrNull(JSONObject j, String name) throws JSONException {
189         if (j.isNull(name)) {
190             return null;
191         }
192         return j.getString(name);
193     }
194 
getConfigRequest(JSONObject j)195     private static ConfigRequest getConfigRequest(JSONObject j) throws JSONException {
196         if (j == null) {
197             return null;
198         }
199 
200         ConfigRequest.Builder builder = new ConfigRequest.Builder();
201 
202         if (j.has("Support5gBand")) {
203             builder.setSupport5gBand(j.getBoolean("Support5gBand"));
204         }
205         if (j.has("MasterPreference")) {
206             builder.setMasterPreference(j.getInt("MasterPreference"));
207         }
208         if (j.has("ClusterLow")) {
209             builder.setClusterLow(j.getInt("ClusterLow"));
210         }
211         if (j.has("ClusterHigh")) {
212             builder.setClusterHigh(j.getInt("ClusterHigh"));
213         }
214         if (j.has("DiscoveryWindowInterval")) {
215             JSONArray interval = j.getJSONArray("DiscoveryWindowInterval");
216             if (interval.length() != 2) {
217                 throw new JSONException(
218                         "Expect 'DiscoveryWindowInterval' to be an array with 2 elements!");
219             }
220             int intervalValue = interval.getInt(ConfigRequest.NAN_BAND_24GHZ);
221             if (intervalValue != ConfigRequest.DW_INTERVAL_NOT_INIT) {
222                 builder.setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_24GHZ, intervalValue);
223             }
224             intervalValue = interval.getInt(ConfigRequest.NAN_BAND_5GHZ);
225             if (intervalValue != ConfigRequest.DW_INTERVAL_NOT_INIT) {
226                 builder.setDiscoveryWindowInterval(ConfigRequest.NAN_BAND_5GHZ, intervalValue);
227             }
228         }
229 
230         return builder.build();
231     }
232 
getMatchFilter(JSONArray ja)233     private static List<byte[]> getMatchFilter(JSONArray ja) throws JSONException {
234         List<byte[]> la = new ArrayList<>();
235         for (int i = 0; i < ja.length(); ++i) {
236             la.add(Base64.decode(ja.getString(i).getBytes(StandardCharsets.UTF_8), Base64.DEFAULT));
237         }
238         return la;
239     }
240 
getPublishConfig(JSONObject j)241     private static PublishConfig getPublishConfig(JSONObject j) throws JSONException {
242         if (j == null) {
243             return null;
244         }
245 
246         PublishConfig.Builder builder = new PublishConfig.Builder();
247 
248         if (j.has("ServiceName")) {
249             builder.setServiceName(getStringOrNull(j, "ServiceName"));
250         }
251 
252         if (j.has("ServiceSpecificInfo")) {
253             String ssi = getStringOrNull(j, "ServiceSpecificInfo");
254             if (ssi != null) {
255                 builder.setServiceSpecificInfo(ssi.getBytes());
256             }
257         }
258 
259         if (j.has("MatchFilter")) {
260             byte[] bytes = Base64.decode(
261                     j.getString("MatchFilter").getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
262             List<byte[]> mf = new TlvBufferUtils.TlvIterable(0, 1, bytes).toList();
263             builder.setMatchFilter(mf);
264 
265         }
266 
267         if (!j.isNull("MatchFilterList")) {
268             builder.setMatchFilter(getMatchFilter(j.getJSONArray("MatchFilterList")));
269         }
270 
271         if (j.has("DiscoveryType")) {
272             builder.setPublishType(j.getInt("DiscoveryType"));
273         }
274         if (j.has("TtlSec")) {
275             builder.setTtlSec(j.getInt("TtlSec"));
276         }
277         if (j.has("TerminateNotificationEnabled")) {
278             builder.setTerminateNotificationEnabled(j.getBoolean("TerminateNotificationEnabled"));
279         }
280         if (j.has("RangingEnabled")) {
281             builder.setRangingEnabled(j.getBoolean("RangingEnabled"));
282         }
283 
284 
285         return builder.build();
286     }
287 
getSubscribeConfig(JSONObject j)288     private static SubscribeConfig getSubscribeConfig(JSONObject j) throws JSONException {
289         if (j == null) {
290             return null;
291         }
292 
293         SubscribeConfig.Builder builder = new SubscribeConfig.Builder();
294 
295         if (j.has("ServiceName")) {
296             builder.setServiceName(j.getString("ServiceName"));
297         }
298 
299         if (j.has("ServiceSpecificInfo")) {
300             String ssi = getStringOrNull(j, "ServiceSpecificInfo");
301             if (ssi != null) {
302                 builder.setServiceSpecificInfo(ssi.getBytes());
303             }
304         }
305 
306         if (j.has("MatchFilter")) {
307             byte[] bytes = Base64.decode(
308                     j.getString("MatchFilter").getBytes(StandardCharsets.UTF_8), Base64.DEFAULT);
309             List<byte[]> mf = new TlvBufferUtils.TlvIterable(0, 1, bytes).toList();
310             builder.setMatchFilter(mf);
311         }
312 
313         if (!j.isNull("MatchFilterList")) {
314             builder.setMatchFilter(getMatchFilter(j.getJSONArray("MatchFilterList")));
315         }
316 
317         if (j.has("DiscoveryType")) {
318             builder.setSubscribeType(j.getInt("DiscoveryType"));
319         }
320         if (j.has("TtlSec")) {
321             builder.setTtlSec(j.getInt("TtlSec"));
322         }
323         if (j.has("TerminateNotificationEnabled")) {
324             builder.setTerminateNotificationEnabled(j.getBoolean("TerminateNotificationEnabled"));
325         }
326         if (j.has("MinDistanceMm")) {
327             builder.setMinDistanceMm(j.getInt("MinDistanceMm"));
328         }
329         if (j.has("MaxDistanceMm")) {
330             builder.setMaxDistanceMm(j.getInt("MaxDistanceMm"));
331         }
332 
333         return builder.build();
334     }
335 
WifiAwareManagerFacade(FacadeManager manager)336     public WifiAwareManagerFacade(FacadeManager manager) {
337         super(manager);
338         mService = manager.getService();
339 
340         mMgr = (WifiAwareManager) mService.getSystemService(Context.WIFI_AWARE_SERVICE);
341 
342         mEventFacade = manager.getReceiver(EventFacade.class);
343 
344         mStateChangedReceiver = new WifiAwareStateChangedReceiver();
345         IntentFilter filter = new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
346         mService.registerReceiver(mStateChangedReceiver, filter);
347     }
348 
349     @Override
shutdown()350     public void shutdown() {
351         wifiAwareDestroyAll();
352         mService.unregisterReceiver(mStateChangedReceiver);
353     }
354 
355     @Rpc(description = "Does the device support the Wi-Fi Aware feature?")
doesDeviceSupportWifiAwareFeature()356     public Boolean doesDeviceSupportWifiAwareFeature() {
357         return mService.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
358     }
359 
360     @Rpc(description = "Is Aware Usage Enabled?")
wifiIsAwareAvailable()361     public Boolean wifiIsAwareAvailable() throws RemoteException {
362         synchronized (mLock) {
363             return mMgr.isAvailable();
364         }
365     }
366 
367     @Rpc(description = "Destroy all Aware sessions and discovery sessions")
wifiAwareDestroyAll()368     public void wifiAwareDestroyAll() {
369         synchronized (mLock) {
370             for (int i = 0; i < mSessions.size(); ++i) {
371                 mSessions.valueAt(i).close();
372             }
373             mSessions.clear();
374 
375             /* discovery sessions automatically destroyed when containing Aware sessions
376              * destroyed */
377             mDiscoverySessions.clear();
378 
379             mMessageStartTime.clear();
380         }
381     }
382 
383     @Rpc(description = "Attach to Aware.")
wifiAwareAttach( @pcParametername = "identityCb", description = "Controls whether an identity callback is provided") @pcOptional Boolean identityCb, @RpcParameter(name = "awareConfig", description = "The session configuration, or null for default config") @RpcOptional JSONObject awareConfig, @RpcParameter(name = "useIdInCallbackEvent", description = "Specifies whether the callback events should be decorated with session Id") @RpcOptional Boolean useIdInCallbackEvent)384     public Integer wifiAwareAttach(
385             @RpcParameter(name = "identityCb",
386                 description = "Controls whether an identity callback is provided")
387                 @RpcOptional Boolean identityCb,
388             @RpcParameter(name = "awareConfig",
389                 description = "The session configuration, or null for default config")
390                 @RpcOptional JSONObject awareConfig,
391             @RpcParameter(name = "useIdInCallbackEvent",
392                 description =
393                     "Specifies whether the callback events should be decorated with session Id")
394                 @RpcOptional Boolean useIdInCallbackEvent)
395             throws RemoteException, JSONException {
396         synchronized (mLock) {
397             int sessionId = getNextSessionId();
398             boolean useIdInCallbackEventName =
399                 (useIdInCallbackEvent != null) ? useIdInCallbackEvent : false;
400             mMgr.attach(null, getConfigRequest(awareConfig),
401                     new AwareAttachCallbackPostsEvents(sessionId, useIdInCallbackEventName),
402                     (identityCb != null && identityCb.booleanValue())
403                         ? new AwareIdentityChangeListenerPostsEvents(sessionId,
404                         useIdInCallbackEventName) : null);
405             return sessionId;
406         }
407     }
408 
409     @Rpc(description = "Destroy a Aware session.")
wifiAwareDestroy( @pcParametername = "clientId", description = "The client ID returned when a connection was created") Integer clientId)410     public void wifiAwareDestroy(
411             @RpcParameter(name = "clientId", description = "The client ID returned when a connection was created") Integer clientId)
412             throws RemoteException, JSONException {
413         WifiAwareSession session;
414         synchronized (mLock) {
415             session = mSessions.get(clientId);
416         }
417         if (session == null) {
418             throw new IllegalStateException(
419                     "Calling WifiAwareDisconnect before session (client ID " + clientId
420                             + ") is ready/or already disconnected");
421         }
422         session.close();
423     }
424 
425     @Rpc(description = "Publish.")
wifiAwarePublish( @pcParametername = "clientId", description = "The client ID returned when a connection was created") Integer clientId, @RpcParameter(name = "publishConfig") JSONObject publishConfig, @RpcParameter(name = "useIdInCallbackEvent", description = "Specifies whether the callback events should be decorated with session Id") @RpcOptional Boolean useIdInCallbackEvent)426     public Integer wifiAwarePublish(
427             @RpcParameter(name = "clientId", description = "The client ID returned when a connection was created") Integer clientId,
428             @RpcParameter(name = "publishConfig") JSONObject publishConfig,
429             @RpcParameter(name = "useIdInCallbackEvent",
430             description =
431                 "Specifies whether the callback events should be decorated with session Id")
432                 @RpcOptional Boolean useIdInCallbackEvent)
433             throws RemoteException, JSONException {
434         synchronized (mLock) {
435             WifiAwareSession session = mSessions.get(clientId);
436             if (session == null) {
437                 throw new IllegalStateException(
438                         "Calling WifiAwarePublish before session (client ID " + clientId
439                                 + ") is ready/or already disconnected");
440             }
441             boolean useIdInCallbackEventName =
442                 (useIdInCallbackEvent != null) ? useIdInCallbackEvent : false;
443 
444             int discoverySessionId = getNextDiscoverySessionId();
445             session.publish(getPublishConfig(publishConfig),
446                 new AwareDiscoverySessionCallbackPostsEvents(discoverySessionId,
447                     useIdInCallbackEventName), null);
448             return discoverySessionId;
449         }
450     }
451 
452     @Rpc(description = "Update Publish.")
wifiAwareUpdatePublish( @pcParametername = "sessionId", description = "The discovery session ID") Integer sessionId, @RpcParameter(name = "publishConfig", description = "Publish configuration") JSONObject publishConfig)453     public void wifiAwareUpdatePublish(
454         @RpcParameter(name = "sessionId", description = "The discovery session ID")
455             Integer sessionId,
456         @RpcParameter(name = "publishConfig", description = "Publish configuration")
457             JSONObject publishConfig)
458         throws RemoteException, JSONException {
459         synchronized (mLock) {
460             DiscoverySession session = mDiscoverySessions.get(sessionId);
461             if (session == null) {
462                 throw new IllegalStateException(
463                     "Calling wifiAwareUpdatePublish before session (session ID "
464                         + sessionId + ") is ready");
465             }
466             if (!(session instanceof PublishDiscoverySession)) {
467                 throw new IllegalArgumentException(
468                     "Calling wifiAwareUpdatePublish with a subscribe session ID");
469             }
470             ((PublishDiscoverySession) session).updatePublish(getPublishConfig(publishConfig));
471         }
472     }
473 
474     @Rpc(description = "Subscribe.")
wifiAwareSubscribe( @pcParametername = "clientId", description = "The client ID returned when a connection was created") Integer clientId, @RpcParameter(name = "subscribeConfig") JSONObject subscribeConfig, @RpcParameter(name = "useIdInCallbackEvent", description = "Specifies whether the callback events should be decorated with session Id") @RpcOptional Boolean useIdInCallbackEvent)475     public Integer wifiAwareSubscribe(
476             @RpcParameter(name = "clientId", description = "The client ID returned when a connection was created") Integer clientId,
477             @RpcParameter(name = "subscribeConfig") JSONObject subscribeConfig,
478             @RpcParameter(name = "useIdInCallbackEvent",
479                 description =
480                 "Specifies whether the callback events should be decorated with session Id")
481                 @RpcOptional Boolean useIdInCallbackEvent)
482             throws RemoteException, JSONException {
483         synchronized (mLock) {
484             WifiAwareSession session = mSessions.get(clientId);
485             if (session == null) {
486                 throw new IllegalStateException(
487                         "Calling WifiAwareSubscribe before session (client ID " + clientId
488                                 + ") is ready/or already disconnected");
489             }
490             boolean useIdInCallbackEventName =
491                 (useIdInCallbackEvent != null) ? useIdInCallbackEvent : false;
492 
493             int discoverySessionId = getNextDiscoverySessionId();
494             session.subscribe(getSubscribeConfig(subscribeConfig),
495                 new AwareDiscoverySessionCallbackPostsEvents(discoverySessionId,
496                     useIdInCallbackEventName), null);
497             return discoverySessionId;
498         }
499     }
500 
501     @Rpc(description = "Update Subscribe.")
wifiAwareUpdateSubscribe( @pcParametername = "sessionId", description = "The discovery session ID") Integer sessionId, @RpcParameter(name = "subscribeConfig", description = "Subscribe configuration") JSONObject subscribeConfig)502     public void wifiAwareUpdateSubscribe(
503         @RpcParameter(name = "sessionId", description = "The discovery session ID")
504             Integer sessionId,
505         @RpcParameter(name = "subscribeConfig", description = "Subscribe configuration")
506             JSONObject subscribeConfig)
507         throws RemoteException, JSONException {
508         synchronized (mLock) {
509             DiscoverySession session = mDiscoverySessions.get(sessionId);
510             if (session == null) {
511                 throw new IllegalStateException(
512                     "Calling wifiAwareUpdateSubscribe before session (session ID "
513                         + sessionId + ") is ready");
514             }
515             if (!(session instanceof SubscribeDiscoverySession)) {
516                 throw new IllegalArgumentException(
517                     "Calling wifiAwareUpdateSubscribe with a publish session ID");
518             }
519             ((SubscribeDiscoverySession) session)
520                 .updateSubscribe(getSubscribeConfig(subscribeConfig));
521         }
522     }
523 
524     @Rpc(description = "Destroy a discovery Session.")
wifiAwareDestroyDiscoverySession( @pcParametername = "sessionId", description = "The discovery session ID returned when session was created using publish or subscribe") Integer sessionId)525     public void wifiAwareDestroyDiscoverySession(
526             @RpcParameter(name = "sessionId", description = "The discovery session ID returned when session was created using publish or subscribe") Integer sessionId)
527             throws RemoteException {
528         synchronized (mLock) {
529             DiscoverySession session = mDiscoverySessions.get(sessionId);
530             if (session == null) {
531                 throw new IllegalStateException(
532                         "Calling WifiAwareTerminateSession before session (session ID "
533                                 + sessionId + ") is ready");
534             }
535             session.close();
536             mDiscoverySessions.remove(sessionId);
537         }
538     }
539 
540     @Rpc(description = "Send peer-to-peer Aware message")
wifiAwareSendMessage( @pcParametername = "sessionId", description = "The session ID returned when session" + " was created using publish or subscribe") Integer sessionId, @RpcParameter(name = "peerId", description = "The ID of the peer being communicated " + "with. Obtained from a previous message or match session.") Integer peerId, @RpcParameter(name = "messageId", description = "Arbitrary handle used for " + "identification of the message in the message status callbacks") Integer messageId, @RpcParameter(name = "message") String message, @RpcParameter(name = "retryCount", description = "Number of retries (0 for none) if " + "transmission fails due to no ACK reception") Integer retryCount)541     public void wifiAwareSendMessage(
542             @RpcParameter(name = "sessionId", description = "The session ID returned when session"
543                     + " was created using publish or subscribe") Integer sessionId,
544             @RpcParameter(name = "peerId", description = "The ID of the peer being communicated "
545                     + "with. Obtained from a previous message or match session.") Integer peerId,
546             @RpcParameter(name = "messageId", description = "Arbitrary handle used for "
547                     + "identification of the message in the message status callbacks")
548                     Integer messageId,
549             @RpcParameter(name = "message") String message,
550             @RpcParameter(name = "retryCount", description = "Number of retries (0 for none) if "
551                     + "transmission fails due to no ACK reception") Integer retryCount)
552                     throws RemoteException {
553         DiscoverySession session;
554         synchronized (mLock) {
555             session = mDiscoverySessions.get(sessionId);
556         }
557         if (session == null) {
558             throw new IllegalStateException(
559                     "Calling WifiAwareSendMessage before session (session ID " + sessionId
560                             + " is ready");
561         }
562         byte[] bytes = null;
563         if (message != null) {
564             bytes = message.getBytes();
565         }
566 
567         synchronized (mLock) {
568             mMessageStartTime.put(messageId, System.currentTimeMillis());
569         }
570         session.sendMessage(new PeerHandle(peerId), messageId, bytes, retryCount);
571     }
572 
573     @Rpc(description = "Create a network specifier to be used when specifying a Aware network request")
wifiAwareCreateNetworkSpecifier( @pcParametername = "sessionId", description = "The session ID returned when session was created using publish or subscribe") Integer sessionId, @RpcParameter(name = "peerId", description = "The ID of the peer (obtained through OnMatch or OnMessageReceived") Integer peerId, @RpcParameter(name = "passphrase", description = "Passphrase of the data-path. Optional, can be empty/null.") @RpcOptional String passphrase, @RpcParameter(name = "pmk", description = "PMK of the data-path (base64 encoded). Optional, can be empty/null.") @RpcOptional String pmk, @RpcParameter(name = "port", description = "Port") @RpcOptional Integer port, @RpcParameter(name = "transportProtocol", description = "Transport protocol") @RpcOptional Integer transportProtocol)574     public String wifiAwareCreateNetworkSpecifier(
575             @RpcParameter(name = "sessionId", description = "The session ID returned when session was created using publish or subscribe")
576                     Integer sessionId,
577             @RpcParameter(name = "peerId", description = "The ID of the peer (obtained through OnMatch or OnMessageReceived")
578                     Integer peerId,
579             @RpcParameter(name = "passphrase",
580                 description = "Passphrase of the data-path. Optional, can be empty/null.")
581                 @RpcOptional String passphrase,
582             @RpcParameter(name = "pmk",
583                 description = "PMK of the data-path (base64 encoded). Optional, can be empty/null.")
584                 @RpcOptional String pmk,
585             @RpcParameter(name = "port", description = "Port") @RpcOptional Integer port,
586             @RpcParameter(name = "transportProtocol", description = "Transport protocol")
587                 @RpcOptional Integer transportProtocol) throws JSONException {
588         DiscoverySession session;
589         synchronized (mLock) {
590             session = mDiscoverySessions.get(sessionId);
591         }
592         if (session == null) {
593             throw new IllegalStateException(
594                     "Calling wifiAwareCreateNetworkSpecifier before session (session ID "
595                             + sessionId + " is ready");
596         }
597         PeerHandle peerHandle = null;
598         if (peerId != null) {
599             peerHandle = new PeerHandle(peerId);
600         }
601         byte[] pmkDecoded = null;
602         if (!TextUtils.isEmpty(pmk)) {
603             pmkDecoded = Base64.decode(pmk, Base64.DEFAULT);
604         }
605 
606         WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(
607                 (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
608                         : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
609                 session instanceof SubscribeDiscoverySession
610                         ? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
611                         : WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
612                 session.getClientId(),
613                 session.getSessionId(),
614                 peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
615                 null, // peerMac (not used in this method)
616                 pmkDecoded,
617                 passphrase,
618                 port == null ? 0 : port.intValue(),
619                 transportProtocol == null ? -1 : transportProtocol.intValue());
620 
621         return getJsonString(ns);
622     }
623 
624     @Rpc(description = "Create a network specifier to be used when specifying an OOB Aware network request")
wifiAwareCreateNetworkSpecifierOob( @pcParametername = "clientId", description = "The client ID") Integer clientId, @RpcParameter(name = "role", description = "The role: INITIATOR(0), RESPONDER(1)") Integer role, @RpcParameter(name = "peerMac", description = "The MAC address of the peer") String peerMac, @RpcParameter(name = "passphrase", description = "Passphrase of the data-path. Optional, can be empty/null.") @RpcOptional String passphrase, @RpcParameter(name = "pmk", description = "PMK of the data-path (base64). Optional, can be empty/null.") @RpcOptional String pmk)625     public String wifiAwareCreateNetworkSpecifierOob(
626             @RpcParameter(name = "clientId",
627                     description = "The client ID")
628                     Integer clientId,
629             @RpcParameter(name = "role", description = "The role: INITIATOR(0), RESPONDER(1)")
630                     Integer role,
631             @RpcParameter(name = "peerMac",
632                     description = "The MAC address of the peer")
633                     String peerMac,
634             @RpcParameter(name = "passphrase",
635                     description = "Passphrase of the data-path. Optional, can be empty/null.")
636             @RpcOptional String passphrase,
637             @RpcParameter(name = "pmk",
638                     description = "PMK of the data-path (base64). Optional, can be empty/null.")
639             @RpcOptional String pmk) throws JSONException {
640         WifiAwareSession session;
641         synchronized (mLock) {
642             session = mSessions.get(clientId);
643         }
644         if (session == null) {
645             throw new IllegalStateException(
646                     "Calling wifiAwareCreateNetworkSpecifierOob before session (client ID "
647                             + clientId + " is ready");
648         }
649         byte[] peerMacBytes = null;
650         if (peerMac != null) {
651             peerMacBytes = HexEncoding.decode(peerMac.toCharArray(), false);
652         }
653         byte[] pmkDecoded = null;
654         if (!TextUtils.isEmpty(pmk)) {
655             pmkDecoded = Base64.decode(pmk, Base64.DEFAULT);
656         }
657 
658         WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(
659                 (peerMacBytes == null) ?
660                         WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
661                         : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
662                 role,
663                 session.getClientId(),
664                 0, // 0 is an invalid session ID
665                 0, // 0 is an invalid peer ID
666                 peerMacBytes,
667                 pmkDecoded,
668                 passphrase,
669                 0, // no port for OOB
670                 -1); // no transport protocol for OOB
671 
672         return getJsonString(ns);
673     }
674 
675     private class AwareAttachCallbackPostsEvents extends AttachCallback {
676         private int mSessionId;
677         private long mCreateTimestampMs;
678         private boolean mUseIdInCallbackEventName;
679 
AwareAttachCallbackPostsEvents(int sessionId, boolean useIdInCallbackEventName)680         public AwareAttachCallbackPostsEvents(int sessionId, boolean useIdInCallbackEventName) {
681             mSessionId = sessionId;
682             mCreateTimestampMs = System.currentTimeMillis();
683             mUseIdInCallbackEventName = useIdInCallbackEventName;
684         }
685 
686         @Override
onAttached(WifiAwareSession session)687         public void onAttached(WifiAwareSession session) {
688             synchronized (mLock) {
689                 mSessions.put(mSessionId, session);
690             }
691 
692             Bundle mResults = new Bundle();
693             mResults.putInt("sessionId", mSessionId);
694             mResults.putLong("latencyMs", System.currentTimeMillis() - mCreateTimestampMs);
695             mResults.putLong("timestampMs", System.currentTimeMillis());
696             if (mUseIdInCallbackEventName) {
697                 mEventFacade.postEvent("WifiAwareOnAttached_" + mSessionId, mResults);
698             } else {
699                 mEventFacade.postEvent("WifiAwareOnAttached", mResults);
700             }
701         }
702 
703         @Override
onAttachFailed()704         public void onAttachFailed() {
705             Bundle mResults = new Bundle();
706             mResults.putInt("sessionId", mSessionId);
707             mResults.putLong("latencyMs", System.currentTimeMillis() - mCreateTimestampMs);
708             if (mUseIdInCallbackEventName) {
709                 mEventFacade.postEvent("WifiAwareOnAttachFailed_" + mSessionId, mResults);
710             } else {
711                 mEventFacade.postEvent("WifiAwareOnAttachFailed", mResults);
712             }
713         }
714     }
715 
716     private class AwareIdentityChangeListenerPostsEvents extends IdentityChangedListener {
717         private int mSessionId;
718         private boolean mUseIdInCallbackEventName;
719 
AwareIdentityChangeListenerPostsEvents(int sessionId, boolean useIdInCallbackEventName)720         public AwareIdentityChangeListenerPostsEvents(int sessionId,
721             boolean useIdInCallbackEventName) {
722             mSessionId = sessionId;
723             mUseIdInCallbackEventName = useIdInCallbackEventName;
724         }
725 
726         @Override
onIdentityChanged(byte[] mac)727         public void onIdentityChanged(byte[] mac) {
728             Bundle mResults = new Bundle();
729             mResults.putInt("sessionId", mSessionId);
730             mResults.putString("mac", String.valueOf(HexEncoding.encode(mac)));
731             mResults.putLong("timestampMs", System.currentTimeMillis());
732             if (mUseIdInCallbackEventName) {
733                 mEventFacade.postEvent("WifiAwareOnIdentityChanged_" + mSessionId, mResults);
734             } else {
735                 mEventFacade.postEvent("WifiAwareOnIdentityChanged", mResults);
736             }
737         }
738     }
739 
740     private class AwareDiscoverySessionCallbackPostsEvents extends
741             DiscoverySessionCallback {
742         private int mDiscoverySessionId;
743         private boolean mUseIdInCallbackEventName;
744         private long mCreateTimestampMs;
745 
AwareDiscoverySessionCallbackPostsEvents(int discoverySessionId, boolean useIdInCallbackEventName)746         public AwareDiscoverySessionCallbackPostsEvents(int discoverySessionId,
747                 boolean useIdInCallbackEventName) {
748             mDiscoverySessionId = discoverySessionId;
749             mUseIdInCallbackEventName = useIdInCallbackEventName;
750             mCreateTimestampMs = System.currentTimeMillis();
751         }
752 
postEvent(String eventName, Bundle results)753         private void postEvent(String eventName, Bundle results) {
754             String finalEventName = eventName;
755             if (mUseIdInCallbackEventName) {
756                 finalEventName += "_" + mDiscoverySessionId;
757             }
758 
759             mEventFacade.postEvent(finalEventName, results);
760         }
761 
762         @Override
onPublishStarted(PublishDiscoverySession discoverySession)763         public void onPublishStarted(PublishDiscoverySession discoverySession) {
764             synchronized (mLock) {
765                 mDiscoverySessions.put(mDiscoverySessionId, discoverySession);
766             }
767 
768             Bundle mResults = new Bundle();
769             mResults.putInt("discoverySessionId", mDiscoverySessionId);
770             mResults.putLong("latencyMs", System.currentTimeMillis() - mCreateTimestampMs);
771             mResults.putLong("timestampMs", System.currentTimeMillis());
772             postEvent("WifiAwareSessionOnPublishStarted", mResults);
773         }
774 
775         @Override
onSubscribeStarted(SubscribeDiscoverySession discoverySession)776         public void onSubscribeStarted(SubscribeDiscoverySession discoverySession) {
777             synchronized (mLock) {
778                 mDiscoverySessions.put(mDiscoverySessionId, discoverySession);
779             }
780 
781             Bundle mResults = new Bundle();
782             mResults.putInt("discoverySessionId", mDiscoverySessionId);
783             mResults.putLong("latencyMs", System.currentTimeMillis() - mCreateTimestampMs);
784             mResults.putLong("timestampMs", System.currentTimeMillis());
785             postEvent("WifiAwareSessionOnSubscribeStarted", mResults);
786         }
787 
788         @Override
onSessionConfigUpdated()789         public void onSessionConfigUpdated() {
790             Bundle mResults = new Bundle();
791             mResults.putInt("discoverySessionId", mDiscoverySessionId);
792             postEvent("WifiAwareSessionOnSessionConfigUpdated", mResults);
793         }
794 
795         @Override
onSessionConfigFailed()796         public void onSessionConfigFailed() {
797             Bundle mResults = new Bundle();
798             mResults.putInt("discoverySessionId", mDiscoverySessionId);
799             postEvent("WifiAwareSessionOnSessionConfigFailed", mResults);
800         }
801 
802         @Override
onSessionTerminated()803         public void onSessionTerminated() {
804             Bundle mResults = new Bundle();
805             mResults.putInt("discoverySessionId", mDiscoverySessionId);
806             postEvent("WifiAwareSessionOnSessionTerminated", mResults);
807         }
808 
createServiceDiscoveredBaseBundle(PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter)809         private Bundle createServiceDiscoveredBaseBundle(PeerHandle peerHandle,
810                 byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
811             Bundle mResults = new Bundle();
812             mResults.putInt("discoverySessionId", mDiscoverySessionId);
813             mResults.putInt("peerId", peerHandle.peerId);
814             mResults.putByteArray("serviceSpecificInfo", serviceSpecificInfo);
815             mResults.putByteArray("matchFilter", new TlvBufferUtils.TlvConstructor(0,
816                     1).allocateAndPut(matchFilter).getArray());
817             ArrayList<String> matchFilterStrings = new ArrayList<>(matchFilter.size());
818             for (byte[] be: matchFilter) {
819                 matchFilterStrings.add(Base64.encodeToString(be, Base64.DEFAULT));
820             }
821             mResults.putStringArrayList("matchFilterList", matchFilterStrings);
822             mResults.putLong("timestampMs", System.currentTimeMillis());
823             return mResults;
824         }
825 
826         @Override
onServiceDiscovered(PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter)827         public void onServiceDiscovered(PeerHandle peerHandle,
828                 byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
829             Bundle mResults = createServiceDiscoveredBaseBundle(peerHandle, serviceSpecificInfo,
830                     matchFilter);
831             postEvent("WifiAwareSessionOnServiceDiscovered", mResults);
832         }
833 
834         @Override
onServiceDiscoveredWithinRange(PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter, int distanceMm)835         public void onServiceDiscoveredWithinRange(PeerHandle peerHandle,
836                 byte[] serviceSpecificInfo,
837                 List<byte[]> matchFilter, int distanceMm) {
838             Bundle mResults = createServiceDiscoveredBaseBundle(peerHandle, serviceSpecificInfo,
839                     matchFilter);
840             mResults.putInt("distanceMm", distanceMm);
841             postEvent("WifiAwareSessionOnServiceDiscovered", mResults);
842         }
843 
844         @Override
onMessageSendSucceeded(int messageId)845         public void onMessageSendSucceeded(int messageId) {
846             Bundle mResults = new Bundle();
847             mResults.putInt("discoverySessionId", mDiscoverySessionId);
848             mResults.putInt("messageId", messageId);
849             synchronized (mLock) {
850                 Long startTime = mMessageStartTime.get(messageId);
851                 if (startTime != null) {
852                     mResults.putLong("latencyMs",
853                             System.currentTimeMillis() - startTime.longValue());
854                     mMessageStartTime.remove(messageId);
855                 }
856             }
857             postEvent("WifiAwareSessionOnMessageSent", mResults);
858         }
859 
860         @Override
onMessageSendFailed(int messageId)861         public void onMessageSendFailed(int messageId) {
862             Bundle mResults = new Bundle();
863             mResults.putInt("discoverySessionId", mDiscoverySessionId);
864             mResults.putInt("messageId", messageId);
865             synchronized (mLock) {
866                 Long startTime = mMessageStartTime.get(messageId);
867                 if (startTime != null) {
868                     mResults.putLong("latencyMs",
869                             System.currentTimeMillis() - startTime.longValue());
870                     mMessageStartTime.remove(messageId);
871                 }
872             }
873             postEvent("WifiAwareSessionOnMessageSendFailed", mResults);
874         }
875 
876         @Override
onMessageReceived(PeerHandle peerHandle, byte[] message)877         public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
878             Bundle mResults = new Bundle();
879             mResults.putInt("discoverySessionId", mDiscoverySessionId);
880             mResults.putInt("peerId", peerHandle.peerId);
881             mResults.putByteArray("message", message); // TODO: base64
882             mResults.putString("messageAsString", new String(message));
883             postEvent("WifiAwareSessionOnMessageReceived", mResults);
884         }
885 
886         @Override
onServiceLost(PeerHandle peerHandle, @WifiAwareManager.DiscoveryLostReasonCode int reason)887         public void onServiceLost(PeerHandle peerHandle, @WifiAwareManager.DiscoveryLostReasonCode
888                 int reason) {
889             Bundle mResults = new Bundle();
890             mResults.putInt("discoverySessionId", mDiscoverySessionId);
891             mResults.putInt("peerId", peerHandle.peerId);
892             mResults.putInt("lostReason", reason);
893             postEvent("WifiAwareSessionOnServiceLost", mResults);
894         }
895     }
896 
897     class WifiAwareRangingListener implements RttManager.RttListener {
898         private int mCallbackId;
899         private int mSessionId;
900 
WifiAwareRangingListener(int callbackId, int sessionId)901         public WifiAwareRangingListener(int callbackId, int sessionId) {
902             mCallbackId = callbackId;
903             mSessionId = sessionId;
904         }
905 
906         @Override
onSuccess(RttResult[] results)907         public void onSuccess(RttResult[] results) {
908             Bundle bundle = new Bundle();
909             bundle.putInt("callbackId", mCallbackId);
910             bundle.putInt("sessionId", mSessionId);
911 
912             Parcelable[] resultBundles = new Parcelable[results.length];
913             for (int i = 0; i < results.length; i++) {
914                 resultBundles[i] = WifiRttManagerFacade.RangingListener.packRttResult(results[i]);
915             }
916             bundle.putParcelableArray("Results", resultBundles);
917 
918             mEventFacade.postEvent("WifiAwareRangingListenerOnSuccess", bundle);
919         }
920 
921         @Override
onFailure(int reason, String description)922         public void onFailure(int reason, String description) {
923             Bundle bundle = new Bundle();
924             bundle.putInt("callbackId", mCallbackId);
925             bundle.putInt("sessionId", mSessionId);
926             bundle.putInt("reason", reason);
927             bundle.putString("description", description);
928             mEventFacade.postEvent("WifiAwareRangingListenerOnFailure", bundle);
929         }
930 
931         @Override
onAborted()932         public void onAborted() {
933             Bundle bundle = new Bundle();
934             bundle.putInt("callbackId", mCallbackId);
935             bundle.putInt("sessionId", mSessionId);
936             mEventFacade.postEvent("WifiAwareRangingListenerOnAborted", bundle);
937         }
938 
939     }
940 
941     class WifiAwareStateChangedReceiver extends BroadcastReceiver {
942         @Override
onReceive(Context c, Intent intent)943         public void onReceive(Context c, Intent intent) {
944             boolean isAvailable = mMgr.isAvailable();
945             if (!isAvailable) {
946                 wifiAwareDestroyAll();
947             }
948             mEventFacade.postEvent(isAvailable ? "WifiAwareAvailable" : "WifiAwareNotAvailable",
949                     new Bundle());
950         }
951     }
952 }
953