1/*
2 * Copyright (C) 2016 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
17package android.net.wifi.aware;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.SdkConstant;
23import android.annotation.SdkConstant.SdkConstantType;
24import android.annotation.SystemService;
25import android.content.Context;
26import android.net.ConnectivityManager;
27import android.net.NetworkRequest;
28import android.net.NetworkSpecifier;
29import android.os.Binder;
30import android.os.Build;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.os.Process;
36import android.os.RemoteException;
37import android.util.Log;
38
39import libcore.util.HexEncoding;
40
41import java.lang.annotation.Retention;
42import java.lang.annotation.RetentionPolicy;
43import java.lang.ref.WeakReference;
44import java.nio.BufferOverflowException;
45import java.util.List;
46
47/**
48 * This class provides the primary API for managing Wi-Fi Aware operations:
49 * discovery and peer-to-peer data connections.
50 * <p>
51 * The class provides access to:
52 * <ul>
53 * <li>Initialize a Aware cluster (peer-to-peer synchronization). Refer to
54 * {@link #attach(AttachCallback, Handler)}.
55 * <li>Create discovery sessions (publish or subscribe sessions). Refer to
56 * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)} and
57 * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
58 * <li>Create a Aware network specifier to be used with
59 * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
60 * to set-up a Aware connection with a peer. Refer to
61 * {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)},
62 * {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)},
63 * {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])}, and
64 * {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)}.
65 * </ul>
66 * <p>
67 *     Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that
68 *     the functionality is available use the {@link #isAvailable()} function. To track
69 *     changes in Aware usability register for the {@link #ACTION_WIFI_AWARE_STATE_CHANGED}
70 *     broadcast. Note that this broadcast is not sticky - you should register for it and then
71 *     check the above API to avoid a race condition.
72 * <p>
73 *     An application must use {@link #attach(AttachCallback, Handler)} to initialize a
74 *     Aware cluster - before making any other Aware operation. Aware cluster membership is a
75 *     device-wide operation - the API guarantees that the device is in a cluster or joins a
76 *     Aware cluster (or starts one if none can be found). Information about attach success (or
77 *     failure) are returned in callbacks of {@link AttachCallback}. Proceed with Aware
78 *     discovery or connection setup only after receiving confirmation that Aware attach
79 *     succeeded - {@link AttachCallback#onAttached(WifiAwareSession)}. When an
80 *     application is finished using Aware it <b>must</b> use the
81 *     {@link WifiAwareSession#close()} API to indicate to the Aware service that the device
82 *     may detach from the Aware cluster. The device will actually disable Aware once the last
83 *     application detaches.
84 * <p>
85 *     Once a Aware attach is confirmed use the
86 *     {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)}
87 *     or
88 *     {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback,
89 *     Handler)} to create publish or subscribe Aware discovery sessions. Events are called on the
90 *     provided callback object {@link DiscoverySessionCallback}. Specifically, the
91 *     {@link DiscoverySessionCallback#onPublishStarted(PublishDiscoverySession)}
92 *     and
93 *     {@link DiscoverySessionCallback#onSubscribeStarted(
94 *SubscribeDiscoverySession)}
95 *     return {@link PublishDiscoverySession} and
96 *     {@link SubscribeDiscoverySession}
97 *     objects respectively on which additional session operations can be performed, e.g. updating
98 *     the session {@link PublishDiscoverySession#updatePublish(PublishConfig)} and
99 *     {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}. Sessions can
100 *     also be used to send messages using the
101 *     {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} APIs. When an
102 *     application is finished with a discovery session it <b>must</b> terminate it using the
103 *     {@link DiscoverySession#close()} API.
104 * <p>
105 *    Creating connections between Aware devices is managed by the standard
106 *    {@link ConnectivityManager#requestNetwork(NetworkRequest,
107 *    ConnectivityManager.NetworkCallback)}.
108 *    The {@link NetworkRequest} object should be constructed with:
109 *    <ul>
110 *        <li>{@link NetworkRequest.Builder#addTransportType(int)} of
111 *        {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
112 *        <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
113 *        {@link WifiAwareSession#createNetworkSpecifierOpen(int, byte[])},
114 *        {@link WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)},
115 *        {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}, or
116 *        {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
117 *    </ul>
118 */
119@SystemService(Context.WIFI_AWARE_SERVICE)
120public class WifiAwareManager {
121    private static final String TAG = "WifiAwareManager";
122    private static final boolean DBG = false;
123    private static final boolean VDBG = false; // STOPSHIP if true
124
125    /**
126     * Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed.
127     * Use the {@link #isAvailable()} to query the current status.
128     * This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
129     * the broadcast to check the current state of Wi-Fi Aware.
130     * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered
131     * components will be launched.
132     */
133    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
134    public static final String ACTION_WIFI_AWARE_STATE_CHANGED =
135            "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED";
136
137    /** @hide */
138    @IntDef({
139            WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, WIFI_AWARE_DATA_PATH_ROLE_RESPONDER})
140    @Retention(RetentionPolicy.SOURCE)
141    public @interface DataPathRole {
142    }
143
144    /**
145     * Connection creation role is that of INITIATOR. Used to create a network specifier string
146     * when requesting a Aware network.
147     *
148     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
149     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
150     * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
151     * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
152     */
153    public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0;
154
155    /**
156     * Connection creation role is that of RESPONDER. Used to create a network specifier string
157     * when requesting a Aware network.
158     *
159     * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle)
160     * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)
161     * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[])
162     * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String)
163     */
164    public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1;
165
166    private final Context mContext;
167    private final IWifiAwareManager mService;
168
169    private final Object mLock = new Object(); // lock access to the following vars
170
171    /** @hide */
172    public WifiAwareManager(Context context, IWifiAwareManager service) {
173        mContext = context;
174        mService = service;
175    }
176
177    /**
178     * Returns the current status of Aware API: whether or not Aware is available. To track
179     * changes in the state of Aware API register for the
180     * {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast.
181     *
182     * @return A boolean indicating whether the app can use the Aware API at this time (true) or
183     * not (false).
184     */
185    public boolean isAvailable() {
186        try {
187            return mService.isUsageEnabled();
188        } catch (RemoteException e) {
189            throw e.rethrowFromSystemServer();
190        }
191    }
192
193    /**
194     * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify
195     * limitations on configurations, e.g. the maximum service name length.
196     *
197     * @return An object specifying configuration limitations of Aware.
198     */
199    public Characteristics getCharacteristics() {
200        try {
201            return mService.getCharacteristics();
202        } catch (RemoteException e) {
203            throw e.rethrowFromSystemServer();
204        }
205    }
206
207    /**
208     * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
209     * create connections to peers. The device will attach to an existing cluster if it can find
210     * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
211     * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
212     * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
213     * Wi-Fi Aware object.
214     * <p>
215     * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
216     * then this function will simply indicate success immediately using the same {@code
217     * attachCallback}.
218     *
219     * @param attachCallback A callback for attach events, extended from
220     * {@link AttachCallback}.
221     * @param handler The Handler on whose thread to execute the callbacks of the {@code
222     * attachCallback} object. If a null is provided then the application's main thread will be
223     *                used.
224     */
225    public void attach(@NonNull AttachCallback attachCallback, @Nullable Handler handler) {
226        attach(handler, null, attachCallback, null);
227    }
228
229    /**
230     * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
231     * create connections to peers. The device will attach to an existing cluster if it can find
232     * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results
233     * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object.
234     * An application <b>must</b> call {@link WifiAwareSession#close()} when done with the
235     * Wi-Fi Aware object.
236     * <p>
237     * Note: a Aware cluster is a shared resource - if the device is already attached to a cluster
238     * then this function will simply indicate success immediately using the same {@code
239     * attachCallback}.
240     * <p>
241     * This version of the API attaches a listener to receive the MAC address of the Aware interface
242     * on startup and whenever it is updated (it is randomized at regular intervals for privacy).
243     * The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
244     * permission to execute this attach request. Otherwise, use the
245     * {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
246     * requirements this listener will wake up the host at regular intervals causing higher power
247     * consumption, do not use it unless the information is necessary (e.g. for OOB discovery).
248     *
249     * @param attachCallback A callback for attach events, extended from
250     * {@link AttachCallback}.
251     * @param identityChangedListener A listener for changed identity, extended from
252     * {@link IdentityChangedListener}.
253     * @param handler The Handler on whose thread to execute the callbacks of the {@code
254     * attachCallback} and {@code identityChangedListener} objects. If a null is provided then the
255     *                application's main thread will be used.
256     */
257    public void attach(@NonNull AttachCallback attachCallback,
258            @NonNull IdentityChangedListener identityChangedListener,
259            @Nullable Handler handler) {
260        attach(handler, null, attachCallback, identityChangedListener);
261    }
262
263    /** @hide */
264    public void attach(Handler handler, ConfigRequest configRequest,
265            AttachCallback attachCallback,
266            IdentityChangedListener identityChangedListener) {
267        if (VDBG) {
268            Log.v(TAG, "attach(): handler=" + handler + ", callback=" + attachCallback
269                    + ", configRequest=" + configRequest + ", identityChangedListener="
270                    + identityChangedListener);
271        }
272
273        if (attachCallback == null) {
274            throw new IllegalArgumentException("Null callback provided");
275        }
276
277        synchronized (mLock) {
278            Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
279
280            try {
281                Binder binder = new Binder();
282                mService.connect(binder, mContext.getOpPackageName(),
283                        new WifiAwareEventCallbackProxy(this, looper, binder, attachCallback,
284                                identityChangedListener), configRequest,
285                        identityChangedListener != null);
286            } catch (RemoteException e) {
287                throw e.rethrowFromSystemServer();
288            }
289        }
290    }
291
292    /** @hide */
293    public void disconnect(int clientId, Binder binder) {
294        if (VDBG) Log.v(TAG, "disconnect()");
295
296        try {
297            mService.disconnect(clientId, binder);
298        } catch (RemoteException e) {
299            throw e.rethrowFromSystemServer();
300        }
301    }
302
303    /** @hide */
304    public void publish(int clientId, Looper looper, PublishConfig publishConfig,
305            DiscoverySessionCallback callback) {
306        if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
307
308        if (callback == null) {
309            throw new IllegalArgumentException("Null callback provided");
310        }
311
312        try {
313            mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
314                    new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
315                            clientId));
316        } catch (RemoteException e) {
317            throw e.rethrowFromSystemServer();
318        }
319    }
320
321    /** @hide */
322    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
323        if (VDBG) {
324            Log.v(TAG, "updatePublish(): clientId=" + clientId + ",sessionId=" + sessionId
325                    + ", config=" + publishConfig);
326        }
327
328        try {
329            mService.updatePublish(clientId, sessionId, publishConfig);
330        } catch (RemoteException e) {
331            throw e.rethrowFromSystemServer();
332        }
333    }
334
335    /** @hide */
336    public void subscribe(int clientId, Looper looper, SubscribeConfig subscribeConfig,
337            DiscoverySessionCallback callback) {
338        if (VDBG) {
339            if (VDBG) {
340                Log.v(TAG,
341                        "subscribe(): clientId=" + clientId + ", config=" + subscribeConfig);
342            }
343        }
344
345        if (callback == null) {
346            throw new IllegalArgumentException("Null callback provided");
347        }
348
349        try {
350            mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
351                    new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
352                            clientId));
353        } catch (RemoteException e) {
354            throw e.rethrowFromSystemServer();
355        }
356    }
357
358    /** @hide */
359    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
360        if (VDBG) {
361            Log.v(TAG, "updateSubscribe(): clientId=" + clientId + ",sessionId=" + sessionId
362                    + ", config=" + subscribeConfig);
363        }
364
365        try {
366            mService.updateSubscribe(clientId, sessionId, subscribeConfig);
367        } catch (RemoteException e) {
368            throw e.rethrowFromSystemServer();
369        }
370    }
371
372    /** @hide */
373    public void terminateSession(int clientId, int sessionId) {
374        if (VDBG) {
375            Log.d(TAG,
376                    "terminateSession(): clientId=" + clientId + ", sessionId=" + sessionId);
377        }
378
379        try {
380            mService.terminateSession(clientId, sessionId);
381        } catch (RemoteException e) {
382            throw e.rethrowFromSystemServer();
383        }
384    }
385
386    /** @hide */
387    public void sendMessage(int clientId, int sessionId, PeerHandle peerHandle, byte[] message,
388            int messageId, int retryCount) {
389        if (peerHandle == null) {
390            throw new IllegalArgumentException(
391                    "sendMessage: invalid peerHandle - must be non-null");
392        }
393
394        if (VDBG) {
395            Log.v(TAG, "sendMessage(): clientId=" + clientId + ", sessionId=" + sessionId
396                    + ", peerHandle=" + peerHandle.peerId + ", messageId="
397                    + messageId + ", retryCount=" + retryCount);
398        }
399
400        try {
401            mService.sendMessage(clientId, sessionId, peerHandle.peerId, message, messageId,
402                    retryCount);
403        } catch (RemoteException e) {
404            throw e.rethrowFromSystemServer();
405        }
406    }
407
408    /** @hide */
409    public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
410            @NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
411        if (VDBG) {
412            Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
413                    + ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
414                    + ", pmk=" + ((pmk == null) ? "null" : "non-null")
415                    + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
416        }
417
418        if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
419                && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
420            throw new IllegalArgumentException(
421                    "createNetworkSpecifier: Invalid 'role' argument when creating a network "
422                            + "specifier");
423        }
424        if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
425                Build.VERSION_CODES.P)) {
426            if (peerHandle == null) {
427                throw new IllegalArgumentException(
428                        "createNetworkSpecifier: Invalid peer handle - cannot be null");
429            }
430        }
431
432        return new WifiAwareNetworkSpecifier(
433                (peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
434                        : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
435                role,
436                clientId,
437                sessionId,
438                peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
439                null, // peerMac (not used in this method)
440                pmk,
441                passphrase,
442                Process.myUid());
443    }
444
445    /** @hide */
446    public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
447            @NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
448        if (VDBG) {
449            Log.v(TAG, "createNetworkSpecifier: role=" + role
450                    + ", pmk=" + ((pmk == null) ? "null" : "non-null")
451                    + ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
452        }
453
454        if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
455                && role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
456            throw new IllegalArgumentException(
457                    "createNetworkSpecifier: Invalid 'role' argument when creating a network "
458                            + "specifier");
459        }
460        if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
461                Build.VERSION_CODES.P)) {
462            if (peer == null) {
463                throw new IllegalArgumentException(
464                        "createNetworkSpecifier: Invalid peer MAC - cannot be null");
465            }
466        }
467        if (peer != null && peer.length != 6) {
468            throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
469        }
470
471        return new WifiAwareNetworkSpecifier(
472                (peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
473                        : WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
474                role,
475                clientId,
476                0, // 0 is an invalid session ID
477                0, // 0 is an invalid peer ID
478                peer,
479                pmk,
480                passphrase,
481                Process.myUid());
482    }
483
484    private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
485        private static final int CALLBACK_CONNECT_SUCCESS = 0;
486        private static final int CALLBACK_CONNECT_FAIL = 1;
487        private static final int CALLBACK_IDENTITY_CHANGED = 2;
488
489        private final Handler mHandler;
490        private final WeakReference<WifiAwareManager> mAwareManager;
491        private final Binder mBinder;
492        private final Looper mLooper;
493
494        /**
495         * Constructs a {@link AttachCallback} using the specified looper.
496         * All callbacks will delivered on the thread of the specified looper.
497         *
498         * @param looper The looper on which to execute the callbacks.
499         */
500        WifiAwareEventCallbackProxy(WifiAwareManager mgr, Looper looper, Binder binder,
501                final AttachCallback attachCallback,
502                final IdentityChangedListener identityChangedListener) {
503            mAwareManager = new WeakReference<>(mgr);
504            mLooper = looper;
505            mBinder = binder;
506
507            if (VDBG) Log.v(TAG, "WifiAwareEventCallbackProxy ctor: looper=" + looper);
508            mHandler = new Handler(looper) {
509                @Override
510                public void handleMessage(Message msg) {
511                    if (DBG) {
512                        Log.d(TAG, "WifiAwareEventCallbackProxy: What=" + msg.what + ", msg="
513                                + msg);
514                    }
515
516                    WifiAwareManager mgr = mAwareManager.get();
517                    if (mgr == null) {
518                        Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
519                        return;
520                    }
521
522                    switch (msg.what) {
523                        case CALLBACK_CONNECT_SUCCESS:
524                            attachCallback.onAttached(
525                                    new WifiAwareSession(mgr, mBinder, msg.arg1));
526                            break;
527                        case CALLBACK_CONNECT_FAIL:
528                            mAwareManager.clear();
529                            attachCallback.onAttachFailed();
530                            break;
531                        case CALLBACK_IDENTITY_CHANGED:
532                            if (identityChangedListener == null) {
533                                Log.e(TAG, "CALLBACK_IDENTITY_CHANGED: null listener.");
534                            } else {
535                                identityChangedListener.onIdentityChanged((byte[]) msg.obj);
536                            }
537                            break;
538                    }
539                }
540            };
541        }
542
543        @Override
544        public void onConnectSuccess(int clientId) {
545            if (VDBG) Log.v(TAG, "onConnectSuccess");
546
547            Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_SUCCESS);
548            msg.arg1 = clientId;
549            mHandler.sendMessage(msg);
550        }
551
552        @Override
553        public void onConnectFail(int reason) {
554            if (VDBG) Log.v(TAG, "onConnectFail: reason=" + reason);
555
556            Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_FAIL);
557            msg.arg1 = reason;
558            mHandler.sendMessage(msg);
559        }
560
561        @Override
562        public void onIdentityChanged(byte[] mac) {
563            if (VDBG) Log.v(TAG, "onIdentityChanged: mac=" + new String(HexEncoding.encode(mac)));
564
565            Message msg = mHandler.obtainMessage(CALLBACK_IDENTITY_CHANGED);
566            msg.obj = mac;
567            mHandler.sendMessage(msg);
568        }
569    }
570
571    private static class WifiAwareDiscoverySessionCallbackProxy extends
572            IWifiAwareDiscoverySessionCallback.Stub {
573        private static final int CALLBACK_SESSION_STARTED = 0;
574        private static final int CALLBACK_SESSION_CONFIG_SUCCESS = 1;
575        private static final int CALLBACK_SESSION_CONFIG_FAIL = 2;
576        private static final int CALLBACK_SESSION_TERMINATED = 3;
577        private static final int CALLBACK_MATCH = 4;
578        private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
579        private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
580        private static final int CALLBACK_MESSAGE_RECEIVED = 7;
581        private static final int CALLBACK_MATCH_WITH_DISTANCE = 8;
582
583        private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
584        private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
585
586        private final WeakReference<WifiAwareManager> mAwareManager;
587        private final boolean mIsPublish;
588        private final DiscoverySessionCallback mOriginalCallback;
589        private final int mClientId;
590
591        private final Handler mHandler;
592        private DiscoverySession mSession;
593
594        WifiAwareDiscoverySessionCallbackProxy(WifiAwareManager mgr, Looper looper,
595                boolean isPublish, DiscoverySessionCallback originalCallback,
596                int clientId) {
597            mAwareManager = new WeakReference<>(mgr);
598            mIsPublish = isPublish;
599            mOriginalCallback = originalCallback;
600            mClientId = clientId;
601
602            if (VDBG) {
603                Log.v(TAG, "WifiAwareDiscoverySessionCallbackProxy ctor: isPublish=" + isPublish);
604            }
605
606            mHandler = new Handler(looper) {
607                @Override
608                public void handleMessage(Message msg) {
609                    if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
610
611                    if (mAwareManager.get() == null) {
612                        Log.w(TAG, "WifiAwareDiscoverySessionCallbackProxy: handleMessage post GC");
613                        return;
614                    }
615
616                    switch (msg.what) {
617                        case CALLBACK_SESSION_STARTED:
618                            onProxySessionStarted(msg.arg1);
619                            break;
620                        case CALLBACK_SESSION_CONFIG_SUCCESS:
621                            mOriginalCallback.onSessionConfigUpdated();
622                            break;
623                        case CALLBACK_SESSION_CONFIG_FAIL:
624                            mOriginalCallback.onSessionConfigFailed();
625                            if (mSession == null) {
626                                /*
627                                 * creation failed (as opposed to update
628                                 * failing)
629                                 */
630                                mAwareManager.clear();
631                            }
632                            break;
633                        case CALLBACK_SESSION_TERMINATED:
634                            onProxySessionTerminated(msg.arg1);
635                            break;
636                        case CALLBACK_MATCH:
637                        case CALLBACK_MATCH_WITH_DISTANCE:
638                            {
639                            List<byte[]> matchFilter = null;
640                            byte[] arg = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2);
641                            try {
642                                matchFilter = new TlvBufferUtils.TlvIterable(0, 1, arg).toList();
643                            } catch (BufferOverflowException e) {
644                                matchFilter = null;
645                                Log.e(TAG, "onServiceDiscovered: invalid match filter byte array '"
646                                        + new String(HexEncoding.encode(arg))
647                                        + "' - cannot be parsed: e=" + e);
648                            }
649                            if (msg.what == CALLBACK_MATCH) {
650                                mOriginalCallback.onServiceDiscovered(new PeerHandle(msg.arg1),
651                                        msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
652                                        matchFilter);
653                            } else {
654                                mOriginalCallback.onServiceDiscoveredWithinRange(
655                                        new PeerHandle(msg.arg1),
656                                        msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
657                                        matchFilter, msg.arg2);
658                            }
659                            break;
660                        }
661                        case CALLBACK_MESSAGE_SEND_SUCCESS:
662                            mOriginalCallback.onMessageSendSucceeded(msg.arg1);
663                            break;
664                        case CALLBACK_MESSAGE_SEND_FAIL:
665                            mOriginalCallback.onMessageSendFailed(msg.arg1);
666                            break;
667                        case CALLBACK_MESSAGE_RECEIVED:
668                            mOriginalCallback.onMessageReceived(new PeerHandle(msg.arg1),
669                                    (byte[]) msg.obj);
670                            break;
671                    }
672                }
673            };
674        }
675
676        @Override
677        public void onSessionStarted(int sessionId) {
678            if (VDBG) Log.v(TAG, "onSessionStarted: sessionId=" + sessionId);
679
680            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_STARTED);
681            msg.arg1 = sessionId;
682            mHandler.sendMessage(msg);
683        }
684
685        @Override
686        public void onSessionConfigSuccess() {
687            if (VDBG) Log.v(TAG, "onSessionConfigSuccess");
688
689            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_SUCCESS);
690            mHandler.sendMessage(msg);
691        }
692
693        @Override
694        public void onSessionConfigFail(int reason) {
695            if (VDBG) Log.v(TAG, "onSessionConfigFail: reason=" + reason);
696
697            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_FAIL);
698            msg.arg1 = reason;
699            mHandler.sendMessage(msg);
700        }
701
702        @Override
703        public void onSessionTerminated(int reason) {
704            if (VDBG) Log.v(TAG, "onSessionTerminated: reason=" + reason);
705
706            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_TERMINATED);
707            msg.arg1 = reason;
708            mHandler.sendMessage(msg);
709        }
710
711        private void onMatchCommon(int messageType, int peerId, byte[] serviceSpecificInfo,
712                byte[] matchFilter, int distanceMm) {
713            Bundle data = new Bundle();
714            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
715            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
716
717            Message msg = mHandler.obtainMessage(messageType);
718            msg.arg1 = peerId;
719            msg.arg2 = distanceMm;
720            msg.setData(data);
721            mHandler.sendMessage(msg);
722        }
723
724        @Override
725        public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
726            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
727
728            onMatchCommon(CALLBACK_MATCH, peerId, serviceSpecificInfo, matchFilter, 0);
729        }
730
731        @Override
732        public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
733                int distanceMm) {
734            if (VDBG) {
735                Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
736            }
737
738            onMatchCommon(CALLBACK_MATCH_WITH_DISTANCE, peerId, serviceSpecificInfo, matchFilter,
739                    distanceMm);
740        }
741
742        @Override
743        public void onMessageSendSuccess(int messageId) {
744            if (VDBG) Log.v(TAG, "onMessageSendSuccess");
745
746            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_SUCCESS);
747            msg.arg1 = messageId;
748            mHandler.sendMessage(msg);
749        }
750
751        @Override
752        public void onMessageSendFail(int messageId, int reason) {
753            if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
754
755            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_FAIL);
756            msg.arg1 = messageId;
757            msg.arg2 = reason;
758            mHandler.sendMessage(msg);
759        }
760
761        @Override
762        public void onMessageReceived(int peerId, byte[] message) {
763            if (VDBG) {
764                Log.v(TAG, "onMessageReceived: peerId=" + peerId);
765            }
766
767            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_RECEIVED);
768            msg.arg1 = peerId;
769            msg.obj = message;
770            mHandler.sendMessage(msg);
771        }
772
773        /*
774         * Proxied methods
775         */
776        public void onProxySessionStarted(int sessionId) {
777            if (VDBG) Log.v(TAG, "Proxy: onSessionStarted: sessionId=" + sessionId);
778            if (mSession != null) {
779                Log.e(TAG,
780                        "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
781                throw new IllegalStateException(
782                        "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
783            }
784
785            WifiAwareManager mgr = mAwareManager.get();
786            if (mgr == null) {
787                Log.w(TAG, "onProxySessionStarted: mgr GC'd");
788                return;
789            }
790
791            if (mIsPublish) {
792                PublishDiscoverySession session = new PublishDiscoverySession(mgr,
793                        mClientId, sessionId);
794                mSession = session;
795                mOriginalCallback.onPublishStarted(session);
796            } else {
797                SubscribeDiscoverySession
798                        session = new SubscribeDiscoverySession(mgr, mClientId, sessionId);
799                mSession = session;
800                mOriginalCallback.onSubscribeStarted(session);
801            }
802        }
803
804        public void onProxySessionTerminated(int reason) {
805            if (VDBG) Log.v(TAG, "Proxy: onSessionTerminated: reason=" + reason);
806            if (mSession != null) {
807                mSession.setTerminated();
808                mSession = null;
809            } else {
810                Log.w(TAG, "Proxy: onSessionTerminated called but mSession is null!?");
811            }
812            mAwareManager.clear();
813            mOriginalCallback.onSessionTerminated();
814        }
815    }
816}
817