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