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