• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi.usd;
18 
19 import android.annotation.NonNull;
20 import android.annotation.SuppressLint;
21 import android.app.AlarmManager;
22 import android.net.MacAddress;
23 import android.net.wifi.IBooleanListener;
24 import android.net.wifi.usd.Characteristics;
25 import android.net.wifi.usd.Config;
26 import android.net.wifi.usd.IPublishSessionCallback;
27 import android.net.wifi.usd.ISubscribeSessionCallback;
28 import android.net.wifi.usd.PublishConfig;
29 import android.net.wifi.usd.PublishSession;
30 import android.net.wifi.usd.PublishSessionCallback;
31 import android.net.wifi.usd.SessionCallback;
32 import android.net.wifi.usd.SubscribeConfig;
33 import android.net.wifi.usd.SubscribeSession;
34 import android.net.wifi.usd.SubscribeSessionCallback;
35 import android.os.Bundle;
36 import android.os.IBinder;
37 import android.os.RemoteCallbackList;
38 import android.os.RemoteException;
39 import android.util.Log;
40 import android.util.SparseArray;
41 
42 import com.android.server.wifi.ActiveModeWarden;
43 import com.android.server.wifi.Clock;
44 import com.android.server.wifi.SupplicantStaIfaceHal;
45 import com.android.server.wifi.WifiThreadRunner;
46 
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.concurrent.Executor;
52 import java.util.function.Consumer;
53 
54 import javax.annotation.concurrent.NotThreadSafe;
55 
56 /**
57  * This class UsdRequestManager acts as central point for handling various USD requests from
58  * applications such as publish, subscribe, send message, etc. It sends the command to HAL to
59  * carry out these actions and expect for callbacks from HAL on various events such as susbcribe/
60  * publish started, service discovered, received a message from the peer, etc.
61  *
62  * <p>Here is how it works,
63  * <ul>
64  * <li>Role: The UsdRequestManager can act as either a publisher or subscriber
65  * <li>Request handling: It manages incoming requests and ensures the new commands are not accepted
66  * while a previous subscribe or publish is still awaiting for the response from HAL.
67  * <li>Session Management: USD session are organized and tracked using unique session IDs. Each
68  * session maintains a collection of USD discovery results which are indexed by the USD peer.
69  * <li>USD Peer: A peer is created for discover and also created a unique id (hash) which maps to
70  * local session id, remote session id and remote mac address. Applications are given this unique
71  * id (hash) on various indications.
72  *
73  * <p>Essentially, this class streamlines USD communication by managing requests, organizing
74  * sessions, and maintaining information about discovered peers. It also enforces a sequential
75  * processing of requests to prevent conflicts and ensure reliable communication with HAL.
76  * </ul>
77  */
78 @NotThreadSafe
79 @SuppressLint("NewApi")
80 public class UsdRequestManager {
81     public static final String TAG = "UsdRequestManager";
82     private static final int DEFAULT_COMMAND_ID = 100;
83     private static final int USD_TEMP_SESSION_ID = 255;
84     private static final int INVALID_ID = -1;
85     private static final String USD_REQUEST_MANAGER_ALARM_TAG = "UsdRequestManagerAlarmTag";
86 
87     /**
88      * A unique peer hash (a unique peer id) generator. Application will get the peer hash as the
89      * identifier of the peer. Also peer hash is globally mapped to a peer (defined by ownId,
90      * peerId and peer mac address).
91      */
92     private static int sNextPeerHash = 100;
93     private final UsdNativeManager mUsdNativeManager;
94     private final ActiveModeWarden mActiveModeWarden;
95     private SupplicantStaIfaceHal.UsdCapabilitiesInternal mUsdCapabilities;
96     private final WifiThreadRunner mWifiThreadRunner;
97     private final Clock mClock;
98     private enum Role {
99         NONE, PUBLISHER, SUBSCRIBER
100     }
101     private Role mRequesterRole;
102     private final AlarmManager mAlarmManager;
103     private final AlarmManager.OnAlarmListener mTimeoutListener = () -> {
104         startCleaningUpExpiredSessions();
105     };
106     private static final int TEMP_SESSION_TIMEOUT_MILLIS = 1000;
107     private static final int TTL_GAP_MILLIS = 1000;
108 
109     private final RemoteCallbackList<IBooleanListener> mPublisherListenerList =
110             new RemoteCallbackList<IBooleanListener>();
111     private final RemoteCallbackList<IBooleanListener> mSubscriberListenerList =
112             new RemoteCallbackList<IBooleanListener>();
113 
startCleaningUpExpiredSessions()114     private void startCleaningUpExpiredSessions() {
115         long current = mClock.getElapsedSinceBootMillis();
116         long nextSchedule = Long.MAX_VALUE;
117         long age;
118         List<Integer> sessionsToDelete = new ArrayList<>();
119 
120         // Cleanup sessions which crossed the TTL.
121         for (int i = 0; i < mUsdSessions.size(); i++) {
122             UsdSession usdSession = mUsdSessions.valueAt(i);
123             int sessionId = mUsdSessions.keyAt(i);
124             int ttlMillis = TEMP_SESSION_TIMEOUT_MILLIS;
125             if (sessionId != USD_TEMP_SESSION_ID) {
126                 ttlMillis = ((usdSession.getRole() == Role.PUBLISHER)
127                         ? usdSession.mPublishConfig.getTtlSeconds()
128                         : usdSession.mSubscribeConfig.getTtlSeconds()) * 1000 + TTL_GAP_MILLIS;
129             }
130             age = current - usdSession.mCreationTimeMillis;
131             if (age >= ttlMillis) {
132                 sessionsToDelete.add(sessionId);
133             } else {
134                 nextSchedule = Math.min(ttlMillis - age, nextSchedule);
135             }
136         }
137 
138         for (int sessionId : sessionsToDelete) {
139             mUsdSessions.get(sessionId).sessionCleanup();
140             mUsdSessions.remove(sessionId);
141         }
142 
143         // Reschedule if necessary.
144         if (mUsdSessions.size() > 0 && nextSchedule < Long.MAX_VALUE) {
145             mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
146                     mClock.getElapsedSinceBootMillis() + nextSchedule,
147                     USD_REQUEST_MANAGER_ALARM_TAG, mTimeoutListener,
148                     mWifiThreadRunner.getHandler());
149         }
150     }
151 
stopCleaningUpExpiredSessions()152     private void stopCleaningUpExpiredSessions() {
153         mAlarmManager.cancel(mTimeoutListener);
154     }
155 
156     /**
157      * A class to represent USD peer. A combination of ownId, peerId and peerMacAddress define a
158      * unique peer.
159      */
160     public static final class UsdPeer {
161         public final int ownId;
162         public final int peerId;
163         public final MacAddress peerMacAddress;
164 
UsdPeer(int ownId, int peerId, MacAddress peerMacAddress)165         public UsdPeer(int ownId, int peerId, MacAddress peerMacAddress) {
166             this.ownId = ownId;
167             this.peerId = peerId;
168             this.peerMacAddress = peerMacAddress;
169         }
170 
171         @Override
equals(Object o)172         public boolean equals(Object o) {
173             if (this == o) return true;
174             if (!(o instanceof UsdPeer peer)) return false;
175             return ownId == peer.ownId && peerId == peer.peerId && peerMacAddress.equals(
176                     peer.peerMacAddress);
177         }
178 
179         @Override
hashCode()180         public int hashCode() {
181             return Objects.hash(ownId, peerId, peerMacAddress);
182         }
183     }
184 
185     /**
186      * A class representing USD session.
187      */
188     private final class UsdSession implements IBinder.DeathRecipient {
189         private int mId = INVALID_ID;
190         private Role mSessionRole = Role.NONE;
191         private PublishConfig mPublishConfig;
192         private IPublishSessionCallback mIPublishSessionCallback;
193         private SubscribeConfig mSubscribeConfig;
194         private ISubscribeSessionCallback mISubscribeSessionCallback;
195         private final long mCreationTimeMillis;
196         /**
197          * Maps peer to peer hash (a unique identifier to the peer).
198          */
199         private final HashMap<UsdPeer, Integer> mSessionPeers = new HashMap<>();
200 
201         /**
202          * Get Role of the session. See {@link Role} for different roles.
203          */
getRole()204         public Role getRole() {
205             return mSessionRole;
206         }
207 
208         /**
209          * Set session id for this session.
210          */
setSessionId(int sessionId)211         public void setSessionId(int sessionId) {
212             mId = sessionId;
213         }
214 
215         /**
216          * Adds a peer to the session if not already there. It creates a unique id (key) and add the
217          * peer to a map.
218          */
addPeerOnce(UsdPeer peer)219         public void addPeerOnce(UsdPeer peer) {
220             if (mSessionPeers.containsKey(peer)) return;
221             int peerHash = sNextPeerHash++;
222             mSessionPeers.put(peer, peerHash);
223             addPeerToGlobalMap(peerHash, peer);
224         }
225 
226         /**
227          * Get unique hash (a unique id) for a peer.
228          */
getPeerHash(UsdPeer peer)229         public int getPeerHash(UsdPeer peer) {
230             return mSessionPeers.getOrDefault(peer, INVALID_ID);
231         }
232 
233         /**
234          * Clear all peers for this session.
235          */
releasePeers()236         public void releasePeers() {
237             // Release all peers associated to this session from global map.
238             for (int peerHash : mSessionPeers.values()) {
239                 removePeerFromGlobalMap(peerHash);
240             }
241             mSessionPeers.clear();
242         }
243 
244         /**
245          * A constructor for publisher session.
246          */
UsdSession(PublishConfig publishConfig, IPublishSessionCallback callback)247         UsdSession(PublishConfig publishConfig, IPublishSessionCallback callback) {
248             mSessionRole = Role.PUBLISHER;
249             mPublishConfig = publishConfig;
250             mIPublishSessionCallback = callback;
251             // Register the recipient for a notification if this binder goes away.
252             try {
253                 callback.asBinder().linkToDeath(this, 0);
254             } catch (RemoteException e) {
255                 Log.e(TAG, "UsdSession linkToDeath " + e);
256             }
257             mCreationTimeMillis = mClock.getElapsedSinceBootMillis();
258         }
259 
260         /**
261          * A constructor for subscriber session.
262          */
UsdSession(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback)263         UsdSession(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback) {
264             mSessionRole = Role.SUBSCRIBER;
265             mSubscribeConfig = subscribeConfig;
266             mISubscribeSessionCallback = callback;
267             // Register the recipient for a notification if this binder goes away.
268             try {
269                 callback.asBinder().linkToDeath(this, 0);
270             } catch (RemoteException e) {
271                 Log.e(TAG, "UsdSession linkToDeath " + e);
272             }
273             mCreationTimeMillis = mClock.getElapsedSinceBootMillis();
274         }
275 
276         @Override
binderDied()277         public void binderDied() {
278             mWifiThreadRunner.post(() -> sessionCleanup());
279         }
280 
281         /**
282          * A sessionCleanup function for the USD session.
283          */
sessionCleanup()284         public void sessionCleanup() {
285             releasePeers();
286             if (mSessionRole == Role.PUBLISHER) {
287                 mIPublishSessionCallback.asBinder().unlinkToDeath(this, 0);
288             } else {
289                 mISubscribeSessionCallback.asBinder().unlinkToDeath(this, 0);
290             }
291             if (isSingleSession()) {
292                 mRequesterRole = Role.NONE;
293                 stopCleaningUpExpiredSessions();
294                 // Once last session is cleaned up, broadcast subscriber/publisher status.
295                 if (mSessionRole == Role.PUBLISHER) {
296                     broadcastSubscriberStatus();
297                 } else {
298                     broadcastPublisherStatus();
299                 }
300             }
301             mUsdSessions.remove(mId);
302             mSessionRole = Role.NONE;
303         }
304     }
305 
306     /**
307      * A class for USD discovery info from HAL.
308      */
309     public static final class UsdHalDiscoveryInfo {
310         public final int ownId;
311         public final int peerId;
312         public MacAddress peerMacAddress;
313         public final byte[] serviceSpecificInfo;
314         @Config.ServiceProtoType
315         public final int serviceProtoType;
316         public final boolean isFsdEnabled;
317         public final byte[] matchFilter;
318 
UsdHalDiscoveryInfo(int ownId, int peerId, MacAddress peerMacAddress, byte[] serviceSpecificInfo, int serviceProtoType, boolean isFsdEnabled, byte[] matchFilter)319         public UsdHalDiscoveryInfo(int ownId, int peerId, MacAddress peerMacAddress,
320                 byte[] serviceSpecificInfo, int serviceProtoType, boolean isFsdEnabled,
321                 byte[] matchFilter) {
322             this.ownId = ownId;
323             this.peerId = peerId;
324             this.peerMacAddress = peerMacAddress;
325             this.serviceSpecificInfo = serviceSpecificInfo;
326             this.serviceProtoType = serviceProtoType;
327             this.isFsdEnabled = isFsdEnabled;
328             this.matchFilter = matchFilter;
329         }
330     }
331 
332     private final SparseArray<UsdSession> mUsdSessions = new SparseArray<>();
333     private final SparseArray<UsdPeer> mGlobalPeerMap = new SparseArray<>();
334 
isSingleSession()335     private boolean isSingleSession() {
336         return mUsdSessions.size() == 1;
337     }
338 
339     /**
340      * Add peer to the global peer map.
341      */
addPeerToGlobalMap(int peerHash, UsdPeer peer)342     private void addPeerToGlobalMap(int peerHash, UsdPeer peer) {
343         mGlobalPeerMap.put(peerHash, peer);
344     }
345 
346     /**
347      * Checks whether peer existing in the global peer map.
348      */
doesPeerExistInGlobalMap(int peerHash)349     private boolean doesPeerExistInGlobalMap(int peerHash) {
350         return mGlobalPeerMap.contains(peerHash);
351     }
352 
353     /**
354      * Gets peer from the global peer map. Returns null if peer does not exist.
355      */
getPeerFromGlobalMap(int peerHash)356     private UsdPeer getPeerFromGlobalMap(int peerHash) {
357         return mGlobalPeerMap.get(peerHash);
358     }
359 
360     /**
361      * Removes peer from global peer map.
362      */
removePeerFromGlobalMap(int peerHash)363     private void removePeerFromGlobalMap(int peerHash) {
364         mGlobalPeerMap.remove(peerHash);
365     }
366 
367     /**
368      * Constructor.
369      */
UsdRequestManager(UsdNativeManager usdNativeManager, WifiThreadRunner wifiThreadRunner, ActiveModeWarden activeModeWarden, Clock clock, AlarmManager alarmManager)370     public UsdRequestManager(UsdNativeManager usdNativeManager, WifiThreadRunner wifiThreadRunner,
371             ActiveModeWarden activeModeWarden, Clock clock, AlarmManager alarmManager) {
372         mUsdNativeManager = usdNativeManager;
373         mActiveModeWarden = activeModeWarden;
374         mWifiThreadRunner = wifiThreadRunner;
375         registerUsdEventsCallback(new UsdNativeEventsCallback());
376         mClock = clock;
377         mAlarmManager = alarmManager;
378         mRequesterRole = Role.NONE;
379     }
380 
isUsdPublisherSupported()381     private boolean isUsdPublisherSupported() {
382         return mUsdCapabilities != null && mUsdCapabilities.isUsdPublisherSupported;
383     }
384 
isUsdSubscriberSupported()385     private boolean isUsdSubscriberSupported() {
386         return mUsdCapabilities != null && mUsdCapabilities.isUsdSubscriberSupported;
387     }
388 
389     /**
390      * Get USD characteristics.
391      */
getCharacteristics()392     public Characteristics getCharacteristics() {
393         Bundle bundle = new Bundle();
394         if (mUsdCapabilities == null) {
395             mUsdCapabilities = mUsdNativeManager.getUsdCapabilities();
396         }
397         if (mUsdCapabilities != null) {
398             bundle.putInt(Characteristics.KEY_MAX_NUM_SUBSCRIBE_SESSIONS,
399                     mUsdCapabilities.maxNumSubscribeSessions);
400             bundle.putInt(Characteristics.KEY_MAX_NUM_PUBLISH_SESSIONS,
401                     mUsdCapabilities.maxNumPublishSessions);
402             bundle.putInt(Characteristics.KEY_MAX_SERVICE_SPECIFIC_INFO_LENGTH,
403                     mUsdCapabilities.maxLocalSsiLengthBytes);
404             bundle.putInt(Characteristics.KEY_MAX_MATCH_FILTER_LENGTH,
405                     mUsdCapabilities.maxMatchFilterLengthBytes);
406             bundle.putInt(Characteristics.KEY_MAX_SERVICE_NAME_LENGTH,
407                     mUsdCapabilities.maxServiceNameLengthBytes);
408         }
409         return new Characteristics(bundle);
410     }
411 
412     /**
413      * Whether subscriber is available.
414      */
isSubscriberAvailable()415     public boolean isSubscriberAvailable() {
416         return mUsdSessions.size() == 0 || mUsdSessions.valueAt(0).mSessionRole == Role.SUBSCRIBER;
417     }
418 
419     /**
420      * Whether publisher is available.
421      */
isPublisherAvailable()422     public boolean isPublisherAvailable() {
423         return mUsdSessions.size() == 0 || mUsdSessions.valueAt(0).mSessionRole == Role.PUBLISHER;
424     }
425 
notifyStatus(IBooleanListener listener, String errMsg, boolean isSuccess)426     private void notifyStatus(IBooleanListener listener, String errMsg, boolean isSuccess) {
427         if (!isSuccess) {
428             Log.e(TAG, "notifyStatus: " + errMsg);
429         }
430         try {
431             listener.onResult(isSuccess);
432         } catch (RemoteException e) {
433             Log.e(TAG, e.toString());
434         }
435     }
436 
getUsdInterfaceName()437     private String getUsdInterfaceName() {
438         return mActiveModeWarden.getPrimaryClientModeManager().getInterfaceName();
439     }
440 
441     /**
442      * See {@link SubscribeSession#sendMessage(int, byte[], Executor, Consumer)} and
443      * {@link PublishSession#sendMessage(int, byte[], Executor, Consumer)}
444      */
sendMessage(int sessionId, int peerHash, @NonNull byte[] message, @NonNull IBooleanListener listener)445     public void sendMessage(int sessionId, int peerHash, @NonNull byte[] message,
446             @NonNull IBooleanListener listener) {
447         if (!isUsdAvailable()) {
448             notifyStatus(listener, "USD is not available", false);
449             return;
450         }
451         if (getUsdInterfaceName() == null) {
452             notifyStatus(listener, "USD interface name is null", false);
453             return;
454         }
455         if (!mUsdSessions.contains(sessionId)) {
456             notifyStatus(listener, "Session does not exist. Session id = " + sessionId, false);
457             return;
458         }
459         if (message.length > mUsdCapabilities.maxLocalSsiLengthBytes) {
460             notifyStatus(listener, "longer message than supported. Max len supported = "
461                     + mUsdCapabilities.maxLocalSsiLengthBytes + " len = " + message.length, false);
462             return;
463         }
464         if (!doesPeerExistInGlobalMap(peerHash)) {
465             notifyStatus(listener, "Invalid peer hash = " + peerHash, false);
466             return;
467         }
468         UsdPeer peer = getPeerFromGlobalMap(peerHash);
469         if (mUsdNativeManager.sendMessage(getUsdInterfaceName(), sessionId, peer.peerId,
470                 peer.peerMacAddress, message)) {
471             notifyStatus(listener, "", true);
472         } else {
473             notifyStatus(listener, "sendMessage failed", false);
474         }
475     }
476 
isUsdAvailable()477     private boolean isUsdAvailable() {
478         if (mRequesterRole == Role.PUBLISHER) {
479             return isPublisherAvailable();
480         } else if (mRequesterRole == Role.SUBSCRIBER) {
481             return isSubscriberAvailable();
482         }
483         return false;
484     }
485 
486     /**
487      * See {@link SubscribeSession#cancel()}
488      */
cancelSubscribe(int sessionId)489     public void cancelSubscribe(int sessionId) {
490         if (getUsdInterfaceName() == null) {
491             Log.e(TAG, "cancelSubscribe: USD interface name is null");
492             return;
493         }
494         if (mRequesterRole == Role.SUBSCRIBER && mUsdSessions.contains(sessionId)) {
495             mUsdNativeManager.cancelSubscribe(getUsdInterfaceName(), sessionId);
496         }
497     }
498 
499     /**
500      * See {@link PublishSession#cancel()}
501      */
cancelPublish(int sessionId)502     public void cancelPublish(int sessionId) {
503         if (getUsdInterfaceName() == null) {
504             Log.e(TAG, "cancelPublish: USD interface name is null");
505             return;
506         }
507         if (mRequesterRole == Role.PUBLISHER && mUsdSessions.contains(sessionId)) {
508             mUsdNativeManager.cancelPublish(getUsdInterfaceName(), sessionId);
509         }
510     }
511 
512     /**
513      * See {@link PublishSession#updatePublish(byte[])}
514      */
updatePublish(int sessionId, byte[] ssi)515     public void updatePublish(int sessionId, byte[] ssi) {
516         if (getUsdInterfaceName() == null) {
517             Log.e(TAG, "updatePublish: USD interface name is null");
518             return;
519         }
520         if (mRequesterRole == Role.PUBLISHER && mUsdSessions.contains(sessionId)
521                 && isPublisherAvailable()) {
522             mUsdNativeManager.updatePublish(getUsdInterfaceName(), sessionId, ssi);
523         }
524     }
525 
notifyPublishFailure(IPublishSessionCallback callback, int reasonCode, String reason)526     private void notifyPublishFailure(IPublishSessionCallback callback, int reasonCode,
527             String reason) {
528         try {
529             Log.w(TAG, reason);
530             callback.onPublishFailed(reasonCode);
531         } catch (RemoteException e) {
532             Log.e(TAG, "publish: " + e);
533         }
534     }
535 
536     /**
537      * See {@link android.net.wifi.usd.UsdManager#publish(PublishConfig, Executor,
538      * PublishSessionCallback)}
539      */
publish(PublishConfig publishConfig, IPublishSessionCallback callback)540     public void publish(PublishConfig publishConfig, IPublishSessionCallback callback) {
541         if (!isUsdPublisherSupported() || !isPublisherAvailable()) {
542             notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Not available");
543             return;
544         }
545         if (getUsdInterfaceName() == null) {
546             notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
547                     "USD interface name is null");
548             return;
549         }
550         // Check if the Role is already taken.
551         if (mRequesterRole == Role.SUBSCRIBER) {
552             notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
553                     "Subscriber is running");
554             return;
555         }
556         if (sessionCreationInProgress()) {
557             notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
558                     "Publish session creation in progress");
559             return;
560         }
561         // Check if maximum sessions reached
562         if (mUsdSessions.size() >= mUsdCapabilities.maxNumPublishSessions) {
563             notifyPublishFailure(callback, SessionCallback.FAILURE_MAX_SESSIONS_REACHED,
564                     "Maximum number of publish sessions reached, num of sessions = "
565                             + mUsdSessions.size());
566             return;
567         }
568         // publish
569         if (mUsdNativeManager.publish(getUsdInterfaceName(), DEFAULT_COMMAND_ID, publishConfig)) {
570             createPublishSession(publishConfig, callback);
571             // Next: onUsdPublishStarted or  onUsdPublishConfigFailed
572         } else {
573             notifyPublishFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Failed");
574         }
575     }
576 
sessionCreationInProgress()577     private boolean sessionCreationInProgress() {
578         return mUsdSessions.contains(USD_TEMP_SESSION_ID);
579     }
580 
notifySubscribeFailure(ISubscribeSessionCallback callback, int reasonCode, String reason)581     private void notifySubscribeFailure(ISubscribeSessionCallback callback, int reasonCode,
582             String reason) {
583         try {
584             Log.w(TAG, reason);
585             callback.onSubscribeFailed(reasonCode);
586         } catch (RemoteException e) {
587             Log.e(TAG, "subscribe: " + e);
588         }
589     }
590 
createPublishSession(PublishConfig config, IPublishSessionCallback callback)591     private void createPublishSession(PublishConfig config, IPublishSessionCallback callback) {
592         UsdSession usdSession = new UsdSession(config, callback);
593         // Use a temp session id. Will get updated in onPublisherStarted.
594         usdSession.setSessionId(USD_TEMP_SESSION_ID);
595         mUsdSessions.put(USD_TEMP_SESSION_ID, usdSession);
596         if (isSingleSession()) {
597             mRequesterRole = Role.PUBLISHER;
598             startCleaningUpExpiredSessions();
599             // After first publisher session is created, notify subscriber status as not available.
600             broadcastSubscriberStatus();
601         }
602     }
603 
createSubscribeSession(SubscribeConfig config, ISubscribeSessionCallback callback)604     private void createSubscribeSession(SubscribeConfig config,
605             ISubscribeSessionCallback callback) {
606         UsdSession usdSession = new UsdSession(config, callback);
607         // Use a temp session id. Will get updated in onSubscriberStarted.
608         usdSession.setSessionId(USD_TEMP_SESSION_ID);
609         mUsdSessions.put(USD_TEMP_SESSION_ID, usdSession);
610         if (isSingleSession()) {
611             mRequesterRole = Role.SUBSCRIBER;
612             startCleaningUpExpiredSessions();
613             // After first subscriber session is created, notify publisher status as not available.
614             broadcastPublisherStatus();
615         }
616     }
617 
618     /**
619      * See {@link android.net.wifi.usd.UsdManager#subscribe(SubscribeConfig, Executor,
620      * SubscribeSessionCallback)}
621      */
subscribe(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback)622     public void subscribe(SubscribeConfig subscribeConfig, ISubscribeSessionCallback callback) {
623         if (!isUsdSubscriberSupported() || !isSubscriberAvailable()) {
624             notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
625                     "Not available");
626             return;
627         }
628         if (getUsdInterfaceName() == null) {
629             notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
630                     "USD interface name is null");
631             return;
632         }
633         // Check if the Role is already taken.
634         if (mRequesterRole == Role.PUBLISHER) {
635             notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
636                     "Publisher is running");
637             return;
638         }
639         if (sessionCreationInProgress()) {
640             notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE,
641                     "Subscribe session creation in progress");
642             return;
643         }
644         // Check if maximum sessions reached
645         if (mUsdSessions.size() >= mUsdCapabilities.maxNumSubscribeSessions) {
646             notifySubscribeFailure(callback, SessionCallback.FAILURE_MAX_SESSIONS_REACHED,
647                     "Maximum number of subscribe sessions reached");
648             return;
649         }
650         // subscribe
651         if (mUsdNativeManager.subscribe(getUsdInterfaceName(), DEFAULT_COMMAND_ID,
652                 subscribeConfig)) {
653             createSubscribeSession(subscribeConfig, callback);
654             // Next: onUsdSubscribeStarted or onUsdSubscribeConfigFailed
655         } else {
656             notifySubscribeFailure(callback, SessionCallback.FAILURE_NOT_AVAILABLE, "Failed");
657         }
658     }
659 
660     /**
661      * Register USD events from HAL.
662      */
registerUsdEventsCallback(UsdNativeEventsCallback usdNativeEventsCallback)663     public void  registerUsdEventsCallback(UsdNativeEventsCallback usdNativeEventsCallback) {
664         mUsdNativeManager.registerUsdEventsCallback(usdNativeEventsCallback);
665     }
666 
667     /**
668      * Validate the session.
669      */
isValidSession(UsdSession session, int sessionId, Role role)670     private static boolean isValidSession(UsdSession session, int sessionId, Role role) {
671         if (session == null) {
672             Log.e(TAG, "isValidSession: session does not exist (id = " + sessionId + ")");
673             return false;
674         }
675         if (session.mSessionRole != role) {
676             Log.e(TAG, "isValidSession: Invalid session role (id = " + sessionId + ")");
677             return false;
678         }
679         if (session.mId != sessionId) {
680             Log.e(TAG, "isValidSession: Invalid session id (id = " + sessionId + ")");
681             return false;
682         }
683         return true;
684     }
685 
686     /**
687      * Implementation of USD callbacks. All callbacks are posted to Wi-Fi thread from
688      * SupplicantStaIfaceCallbackAidlImpl.
689      */
690     public class UsdNativeEventsCallback implements UsdNativeManager.UsdEventsCallback {
691         @Override
onUsdPublishStarted(int cmdId, int publishId)692         public void onUsdPublishStarted(int cmdId, int publishId) {
693             if (cmdId != DEFAULT_COMMAND_ID) {
694                 Log.e(TAG, "onUsdPublishStarted: Invalid command id = " + cmdId);
695                 return;
696             }
697             UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID);
698             if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.PUBLISHER)) return;
699             mUsdSessions.put(publishId, usdSession);
700             usdSession.setSessionId(publishId);
701             mUsdSessions.remove(USD_TEMP_SESSION_ID);
702             try {
703                 usdSession.mIPublishSessionCallback.onPublishStarted(publishId);
704             } catch (RemoteException e) {
705                 Log.e(TAG, "onUsdPublishStarted " + e);
706             }
707             // Next: onUsdPublishReplied or onUsdPublishTerminated
708         }
709 
710         @Override
onUsdSubscribeStarted(int cmdId, int subscribeId)711         public void onUsdSubscribeStarted(int cmdId, int subscribeId) {
712             if (cmdId != DEFAULT_COMMAND_ID) {
713                 Log.e(TAG, "onUsdSubscribeStarted: Invalid command id = " + cmdId);
714                 return;
715             }
716             UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID);
717             if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.SUBSCRIBER)) return;
718             mUsdSessions.put(subscribeId, usdSession);
719             usdSession.setSessionId(subscribeId);
720             mUsdSessions.remove(USD_TEMP_SESSION_ID);
721             try {
722                 usdSession.mISubscribeSessionCallback.onSubscribeStarted(subscribeId);
723             } catch (RemoteException e) {
724                 Log.e(TAG, "onUsdSubscribeStarted " + e);
725             }
726             // Next: onUsdServiceDiscovered or onUsdSubscribeTerminated
727         }
728 
729         @Override
onUsdPublishConfigFailed(int cmdId, @SessionCallback.FailureCode int errorCode)730         public void onUsdPublishConfigFailed(int cmdId,
731                 @SessionCallback.FailureCode int errorCode) {
732             if (cmdId != DEFAULT_COMMAND_ID) {
733                 Log.e(TAG, "onUsdPublishConfigFailed: Invalid command id = " + cmdId);
734                 return;
735             }
736             UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID);
737             if (isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.PUBLISHER)) return;
738             usdSession.sessionCleanup();
739             try {
740                 usdSession.mIPublishSessionCallback.onPublishFailed(errorCode);
741             } catch (RemoteException e) {
742                 Log.e(TAG, "onUsdPublishConfigFailed " + e);
743             }
744         }
745 
746         @Override
onUsdSubscribeConfigFailed(int cmdId, @SessionCallback.FailureCode int errorCode)747         public void onUsdSubscribeConfigFailed(int cmdId,
748                 @SessionCallback.FailureCode int errorCode) {
749             if (cmdId != DEFAULT_COMMAND_ID) {
750                 Log.e(TAG, "onUsdSubscribeConfigFailed: Invalid command id = " + cmdId);
751                 return;
752             }
753             UsdSession usdSession = mUsdSessions.get(USD_TEMP_SESSION_ID);
754             if (!isValidSession(usdSession, USD_TEMP_SESSION_ID, Role.SUBSCRIBER)) return;
755             usdSession.sessionCleanup();
756             try {
757                 usdSession.mISubscribeSessionCallback.onSubscribeFailed(errorCode);
758             } catch (RemoteException e) {
759                 Log.e(TAG, "onUsdSubscribeConfigFailed " + e);
760             }
761         }
762 
763         @Override
onUsdPublishTerminated(int publishId, int reasonCode)764         public void onUsdPublishTerminated(int publishId, int reasonCode) {
765             if (!mUsdSessions.contains(publishId)) {
766                 return;
767             }
768             UsdSession usdSession = mUsdSessions.get(publishId);
769             if (!isValidSession(usdSession, publishId, Role.PUBLISHER)) return;
770             try {
771                 usdSession.mIPublishSessionCallback.onPublishSessionTerminated(reasonCode);
772             } catch (RemoteException e) {
773                 Log.e(TAG, "onUsdPublishTerminated " + e);
774             }
775             usdSession.sessionCleanup();
776         }
777 
778         @Override
onUsdSubscribeTerminated(int subscribeId, int reasonCode)779         public void onUsdSubscribeTerminated(int subscribeId, int reasonCode) {
780             if (!mUsdSessions.contains(subscribeId)) {
781                 return;
782             }
783             UsdSession usdSession = mUsdSessions.get(subscribeId);
784             if (!isValidSession(usdSession, subscribeId, Role.SUBSCRIBER)) return;
785             try {
786                 usdSession.mISubscribeSessionCallback.onSubscribeSessionTerminated(reasonCode);
787             } catch (RemoteException e) {
788                 Log.e(TAG, "onUsdSubscribeTerminated " + e);
789             }
790             usdSession.sessionCleanup();
791         }
792 
793         @Override
onUsdPublishReplied(UsdHalDiscoveryInfo info)794         public void onUsdPublishReplied(UsdHalDiscoveryInfo info) {
795             // Check whether session matches.
796             if (!mUsdSessions.contains(info.ownId)) {
797                 return;
798             }
799             // Check whether events are enabled for the publisher.
800             UsdSession usdSession = mUsdSessions.get(info.ownId);
801             if (isValidSession(usdSession, info.ownId, Role.PUBLISHER)) return;
802             if (!usdSession.mPublishConfig.isEventsEnabled()) return;
803             // Add the peer to the session if not already present.
804             UsdPeer peer = new UsdPeer(info.ownId, info.peerId, info.peerMacAddress);
805             usdSession.addPeerOnce(peer);
806             try {
807                 // Pass unique peer hash to the application. When the application gives back the
808                 // peer hash, it'll be used to retrieve the peer.
809                 usdSession.mIPublishSessionCallback.onPublishReplied(usdSession.getPeerHash(peer),
810                         info.serviceSpecificInfo, info.serviceProtoType, info.isFsdEnabled);
811             } catch (RemoteException e) {
812                 Log.e(TAG, "onUsdPublishReplied " + e);
813             }
814         }
815 
816         @Override
onUsdServiceDiscovered(UsdHalDiscoveryInfo info)817         public void onUsdServiceDiscovered(UsdHalDiscoveryInfo info) {
818             // Check whether session matches.
819             if (!mUsdSessions.contains(info.ownId)) {
820                 return;
821             }
822             // Add the peer to the session if not already present.
823             UsdPeer peer = new UsdPeer(info.ownId, info.peerId, info.peerMacAddress);
824             UsdSession usdSession = mUsdSessions.get(info.ownId);
825             if (isValidSession(usdSession, info.ownId, Role.SUBSCRIBER)) return;
826             usdSession.addPeerOnce(peer);
827             try {
828                 // Pass unique peer hash to the application. When the application gives back the
829                 // peer hash, it'll be used to retrieve the peer.
830                 usdSession.mISubscribeSessionCallback.onSubscribeDiscovered(
831                         usdSession.getPeerHash(peer), info.serviceSpecificInfo,
832                         info.serviceProtoType, info.isFsdEnabled);
833             } catch (RemoteException e) {
834                 Log.e(TAG, "onUsdServiceDiscovered " + e);
835             }
836         }
837 
838         @Override
onUsdMessageReceived(int ownId, int peerId, MacAddress peerMacAddress, byte[] message)839         public void onUsdMessageReceived(int ownId, int peerId, MacAddress peerMacAddress,
840                 byte[] message) {
841             // Check whether session matches.
842             if (!mUsdSessions.contains(ownId)) {
843                 return;
844             }
845             // Add the peer to the session if not already present.
846             UsdPeer peer = new UsdPeer(ownId, peerId, peerMacAddress);
847             UsdSession usdSession = mUsdSessions.get(ownId);
848             if (isValidSession(usdSession, ownId, mRequesterRole)) return;
849             usdSession.addPeerOnce(peer);
850             try {
851                 // Pass unique peer hash to the application. When the application gives back the
852                 // peer hash, it'll be used to retrieve the peer.
853                 if (mRequesterRole == Role.SUBSCRIBER) {
854                     usdSession.mISubscribeSessionCallback.onMessageReceived(
855                             usdSession.getPeerHash(peer), message);
856                 } else {
857                     usdSession.mIPublishSessionCallback.onMessageReceived(
858                             usdSession.getPeerHash(peer), message);
859                 }
860             } catch (RemoteException e) {
861                 Log.e(TAG, "onUsdMessageReceived " + e);
862             }
863         }
864     }
865 
broadcastPublisherStatus()866     private void broadcastPublisherStatus() {
867         int numListeners = mPublisherListenerList.beginBroadcast();
868         for (int i = 0; i < numListeners; i++) {
869             IBooleanListener listener = mPublisherListenerList.getBroadcastItem(i);
870             try {
871                 listener.onResult(isPublisherAvailable());
872             } catch (RemoteException e) {
873                 Log.e(TAG, "broadcastPublisherStatus: " + e);
874             }
875         }
876         mPublisherListenerList.finishBroadcast();
877     }
878 
broadcastSubscriberStatus()879     private void broadcastSubscriberStatus() {
880         int numListeners = mSubscriberListenerList.beginBroadcast();
881         for (int i = 0; i < numListeners; i++) {
882             IBooleanListener listener = mSubscriberListenerList.getBroadcastItem(i);
883             try {
884                 listener.onResult(isSubscriberAvailable());
885             } catch (RemoteException e) {
886                 Log.e(TAG, "broadcastSubscriberStatus: " + e);
887             }
888         }
889         mSubscriberListenerList.finishBroadcast();
890     }
891 
892     /**
893      * Register for publisher status listener and notify the application on current status.
894      */
registerPublisherStatusListener(IBooleanListener listener)895     public void registerPublisherStatusListener(IBooleanListener listener) {
896         mPublisherListenerList.register(listener);
897         try {
898             listener.onResult(isPublisherAvailable());
899         } catch (RemoteException e) {
900             Log.e(TAG, "registerPublisherStatusListener: " + e);
901         }
902     }
903 
904     /**
905      * Unregister previously registered publisher status listener.
906      */
unregisterPublisherStatusListener(IBooleanListener listener)907     public void unregisterPublisherStatusListener(IBooleanListener listener) {
908         mPublisherListenerList.unregister(listener);
909     }
910 
911     /**
912      * Register for subscriber status listener and notify the application on current status.
913      */
registerSubscriberStatusListener(IBooleanListener listener)914     public void registerSubscriberStatusListener(IBooleanListener listener) {
915         mSubscriberListenerList.register(listener);
916         try {
917             listener.onResult(isSubscriberAvailable());
918         } catch (RemoteException e) {
919             Log.e(TAG, "registerSubscriberStatusListener: " + e);
920         }
921     }
922 
923     /**
924      * Unregister previously registered subscriber status listener.
925      */
unregisterSubscriberStatusListener(IBooleanListener listener)926     public void unregisterSubscriberStatusListener(IBooleanListener listener) {
927         mSubscriberListenerList.unregister(listener);
928     }
929 }
930