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.NonNull;
20import android.annotation.Nullable;
21import android.annotation.SystemApi;
22import android.net.NetworkSpecifier;
23import android.os.Binder;
24import android.os.Handler;
25import android.os.Looper;
26import android.util.Log;
27
28import com.android.internal.annotations.VisibleForTesting;
29
30import dalvik.system.CloseGuard;
31
32import java.lang.ref.WeakReference;
33
34/**
35 * This class represents a Wi-Fi Aware session - an attachment to the Wi-Fi Aware service through
36 * which the app can execute discovery operations.
37 */
38public class WifiAwareSession implements AutoCloseable {
39    private static final String TAG = "WifiAwareSession";
40    private static final boolean DBG = false;
41    private static final boolean VDBG = false; // STOPSHIP if true
42
43    private final WeakReference<WifiAwareManager> mMgr;
44    private final Binder mBinder;
45    private final int mClientId;
46
47    private boolean mTerminated = true;
48    private final CloseGuard mCloseGuard = CloseGuard.get();
49
50    /** @hide */
51    public WifiAwareSession(WifiAwareManager manager, Binder binder, int clientId) {
52        if (VDBG) Log.v(TAG, "New session created: manager=" + manager + ", clientId=" + clientId);
53
54        mMgr = new WeakReference<>(manager);
55        mBinder = binder;
56        mClientId = clientId;
57        mTerminated = false;
58
59        mCloseGuard.open("close");
60    }
61
62    /**
63     * Destroy the Wi-Fi Aware service session and, if no other applications are attached to Aware,
64     * also disable Aware. This method destroys all outstanding operations - i.e. all publish and
65     * subscribes are terminated, and any outstanding data-links are shut-down. However, it is
66     * good practice to destroy these discovery sessions and connections explicitly before a
67     * session-wide destroy.
68     * <p>
69     * An application may re-attach after a destroy using
70     * {@link WifiAwareManager#attach(AttachCallback, Handler)} .
71     */
72    @Override
73    public void close() {
74        WifiAwareManager mgr = mMgr.get();
75        if (mgr == null) {
76            Log.w(TAG, "destroy: called post GC on WifiAwareManager");
77            return;
78        }
79        mgr.disconnect(mClientId, mBinder);
80        mTerminated = true;
81        mMgr.clear();
82        mCloseGuard.close();
83    }
84
85    /** @hide */
86    @Override
87    protected void finalize() throws Throwable {
88        try {
89            if (mCloseGuard != null) {
90                mCloseGuard.warnIfOpen();
91            }
92
93            if (!mTerminated) {
94                close();
95            }
96        } finally {
97            super.finalize();
98        }
99    }
100
101    /**
102     * Access the client ID of the Aware session.
103     *
104     * Note: internal visibility for testing.
105     *
106     * @return The internal client ID.
107     *
108     * @hide
109     */
110    @VisibleForTesting
111    public int getClientId() {
112        return mClientId;
113    }
114
115    /**
116     * Issue a request to the Aware service to create a new Aware publish discovery session, using
117     * the specified {@code publishConfig} configuration. The results of the publish operation
118     * are routed to the callbacks of {@link DiscoverySessionCallback}:
119     * <ul>
120     *     <li>
121     *     {@link DiscoverySessionCallback#onPublishStarted(
122     *PublishDiscoverySession)}
123     *     is called when the publish session is created and provides a handle to the session.
124     *     Further operations on the publish session can be executed on that object.
125     *     <li>{@link DiscoverySessionCallback#onSessionConfigFailed()} is called if the
126     *     publish operation failed.
127     * </ul>
128     * <p>
129     * Other results of the publish session operations will also be routed to callbacks
130     * on the {@code callback} object. The resulting publish session can be modified using
131     * {@link PublishDiscoverySession#updatePublish(PublishConfig)}.
132     * <p>
133     *      An application must use the {@link DiscoverySession#close()} to
134     *      terminate the publish discovery session once it isn't needed. This will free
135     *      resources as well terminate any on-air transmissions.
136     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
137     * permission to start a publish discovery session.
138     *
139     * @param publishConfig The {@link PublishConfig} specifying the
140     *            configuration of the requested publish session.
141     * @param callback A {@link DiscoverySessionCallback} derived object to be used for
142     *                 session event callbacks.
143     * @param handler The Handler on whose thread to execute the callbacks of the {@code
144     * callback} object. If a null is provided then the application's main thread will be used.
145     */
146    public void publish(@NonNull PublishConfig publishConfig,
147            @NonNull DiscoverySessionCallback callback, @Nullable Handler handler) {
148        WifiAwareManager mgr = mMgr.get();
149        if (mgr == null) {
150            Log.e(TAG, "publish: called post GC on WifiAwareManager");
151            return;
152        }
153        if (mTerminated) {
154            Log.e(TAG, "publish: called after termination");
155            return;
156        }
157        mgr.publish(mClientId, (handler == null) ? Looper.getMainLooper() : handler.getLooper(),
158                publishConfig, callback);
159    }
160
161    /**
162     * Issue a request to the Aware service to create a new Aware subscribe discovery session, using
163     * the specified {@code subscribeConfig} configuration. The results of the subscribe
164     * operation are routed to the callbacks of {@link DiscoverySessionCallback}:
165     * <ul>
166     *     <li>
167     *  {@link DiscoverySessionCallback#onSubscribeStarted(
168     *SubscribeDiscoverySession)}
169     *     is called when the subscribe session is created and provides a handle to the session.
170     *     Further operations on the subscribe session can be executed on that object.
171     *     <li>{@link DiscoverySessionCallback#onSessionConfigFailed()} is called if the
172     *     subscribe operation failed.
173     * </ul>
174     * <p>
175     * Other results of the subscribe session operations will also be routed to callbacks
176     * on the {@code callback} object. The resulting subscribe session can be modified using
177     * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}.
178     * <p>
179     *      An application must use the {@link DiscoverySession#close()} to
180     *      terminate the subscribe discovery session once it isn't needed. This will free
181     *      resources as well terminate any on-air transmissions.
182     * <p>The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
183     * permission to start a subscribe discovery session.
184     *
185     * @param subscribeConfig The {@link SubscribeConfig} specifying the
186     *            configuration of the requested subscribe session.
187     * @param callback A {@link DiscoverySessionCallback} derived object to be used for
188     *                 session event callbacks.
189     * @param handler The Handler on whose thread to execute the callbacks of the {@code
190     * callback} object. If a null is provided then the application's main thread will be used.
191     */
192    public void subscribe(@NonNull SubscribeConfig subscribeConfig,
193            @NonNull DiscoverySessionCallback callback, @Nullable Handler handler) {
194        WifiAwareManager mgr = mMgr.get();
195        if (mgr == null) {
196            Log.e(TAG, "publish: called post GC on WifiAwareManager");
197            return;
198        }
199        if (mTerminated) {
200            Log.e(TAG, "publish: called after termination");
201            return;
202        }
203        mgr.subscribe(mClientId, (handler == null) ? Looper.getMainLooper() : handler.getLooper(),
204                subscribeConfig, callback);
205    }
206
207    /**
208     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
209     * an unencrypted WiFi Aware connection (link) to the specified peer. The
210     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
211     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
212     * <p>
213     *     This API is targeted for applications which can obtain the peer MAC address using OOB
214     *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
215     *     when using Aware discovery use the alternative network specifier method -
216     *     {@link DiscoverySession#createNetworkSpecifierOpen(PeerHandle)}.
217     * <p>
218     * To set up an encrypted link use the
219     * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} API.
220     *
221     * @param role  The role of this device:
222     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
223     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
224     * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
225     *              value is used to gate the acceptance of a connection request from only that
226     *              peer.
227     *
228     * @return A {@link NetworkSpecifier} to be used to construct
229     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
230     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
231     * android.net.ConnectivityManager.NetworkCallback)}
232     * [or other varieties of that API].
233     */
234    public NetworkSpecifier createNetworkSpecifierOpen(
235            @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer) {
236        WifiAwareManager mgr = mMgr.get();
237        if (mgr == null) {
238            Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager");
239            return null;
240        }
241        if (mTerminated) {
242            Log.e(TAG, "createNetworkSpecifierOpen: called after termination");
243            return null;
244        }
245        return mgr.createNetworkSpecifier(mClientId, role, peer, null, null);
246    }
247
248    /**
249     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
250     * an encrypted WiFi Aware connection (link) to the specified peer. The
251     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
252     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
253     * <p>
254     *     This API is targeted for applications which can obtain the peer MAC address using OOB
255     *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
256     *     when using Aware discovery use the alternative network specifier method -
257     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
258     *
259     * @param role  The role of this device:
260     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
261     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
262     * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
263     *              value is used to gate the acceptance of a connection request from only that
264     *              peer.
265     * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from
266     *                   the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to
267     *                   specify an open (unencrypted) link.
268     *
269     * @return A {@link NetworkSpecifier} to be used to construct
270     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
271     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
272     * android.net.ConnectivityManager.NetworkCallback)}
273     * [or other varieties of that API].
274     */
275    public NetworkSpecifier createNetworkSpecifierPassphrase(
276            @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer,
277            @NonNull String passphrase) {
278        WifiAwareManager mgr = mMgr.get();
279        if (mgr == null) {
280            Log.e(TAG, "createNetworkSpecifierPassphrase: called post GC on WifiAwareManager");
281            return null;
282        }
283        if (mTerminated) {
284            Log.e(TAG, "createNetworkSpecifierPassphrase: called after termination");
285            return null;
286        }
287        if (!WifiAwareUtils.validatePassphrase(passphrase)) {
288            throw new IllegalArgumentException("Passphrase must meet length requirements");
289        }
290
291        return mgr.createNetworkSpecifier(mClientId, role, peer, null, passphrase);
292    }
293
294    /**
295     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} for
296     * an encrypted WiFi Aware connection (link) to the specified peer. The
297     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
298     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
299     * <p>
300     *     This API is targeted for applications which can obtain the peer MAC address using OOB
301     *     (out-of-band) discovery. Aware discovery does not provide the MAC address of the peer -
302     *     when using Aware discovery use the alternative network specifier method -
303     *     {@link DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String)}.
304     *
305     * @param role  The role of this device:
306     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_INITIATOR} or
307     *              {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}
308     * @param peer  The MAC address of the peer's Aware discovery interface. On a RESPONDER this
309     *              value is used to gate the acceptance of a connection request from only that
310     *              peer.
311     * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for
312     *            encrypting the data-path. Use the
313     *            {@link #createNetworkSpecifierPassphrase(int, byte[], String)} to specify a
314     *            Passphrase or {@link #createNetworkSpecifierOpen(int, byte[])} to specify an
315     *            open (unencrypted) link.
316     *
317     * @return A {@link NetworkSpecifier} to be used to construct
318     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to
319     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,
320     * android.net.ConnectivityManager.NetworkCallback)}
321     * [or other varieties of that API].
322     *
323     * @hide
324     */
325    @SystemApi
326    public NetworkSpecifier createNetworkSpecifierPmk(
327            @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull byte[] pmk) {
328        WifiAwareManager mgr = mMgr.get();
329        if (mgr == null) {
330            Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager");
331            return null;
332        }
333        if (mTerminated) {
334            Log.e(TAG, "createNetworkSpecifierPmk: called after termination");
335            return null;
336        }
337        if (!WifiAwareUtils.validatePmk(pmk)) {
338            throw new IllegalArgumentException("PMK must 32 bytes");
339        }
340        return mgr.createNetworkSpecifier(mClientId, role, peer, pmk, null);
341    }
342}
343