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