1 /*
2  * Copyright (C) 2021 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;
18 
19 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_PRIMARY;
20 import static com.android.server.wifi.ActiveModeManager.ROLE_CLIENT_SECONDARY_LONG_LIVED;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.net.wifi.ScanResult;
27 import android.net.wifi.WifiInfo;
28 import android.net.wifi.WifiManager;
29 import android.os.Handler;
30 import android.os.WorkSource;
31 import android.text.TextUtils;
32 import android.util.ArrayMap;
33 import android.util.Log;
34 import android.util.SparseArray;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.modules.utils.build.SdkLevel;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.Map;
44 
45 /**
46  * Manages STA + STA for multi internet networks.
47  */
48 public class MultiInternetManager {
49     private static final String TAG = "WifiMultiInternet";
50 
51     private final ActiveModeWarden mActiveModeWarden;
52     private final FrameworkFacade mFrameworkFacade;
53     private final Context mContext;
54     private final ClientModeImplMonitor mCmiMonitor;
55     private final WifiSettingsStore mSettingsStore;
56     private final Handler mEventHandler;
57     private final Clock mClock;
58     private int mStaConcurrencyMultiInternetMode = WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED;
59     @MultiInternetState
60     private int mMultiInternetConnectionState = MULTI_INTERNET_STATE_NONE;
61     private ConnectionStatusListener mConnectionStatusListener;
62 
63     private SparseArray<NetworkConnectionState> mNetworkConnectionStates = new SparseArray<>();
64     private boolean mVerboseLoggingEnabled = false;
65 
66     /** No multi internet connection needed. */
67     public static final int MULTI_INTERNET_STATE_NONE = 0;
68     /** Multi internet connection is connecting. */
69     public static final int MULTI_INTERNET_STATE_CONNECTION_REQUESTED = 1;
70     /** No multi internet connection is connected. */
71     public static final int MULTI_INTERNET_STATE_CONNECTED = 2;
72     /** @hide */
73     @Retention(RetentionPolicy.SOURCE)
74     @IntDef(prefix = {"MULTI_INTERNET_STATE_"}, value = {
75             MULTI_INTERNET_STATE_NONE,
76             MULTI_INTERNET_STATE_CONNECTION_REQUESTED,
77             MULTI_INTERNET_STATE_CONNECTED})
78     public @interface MultiInternetState {}
79 
80     /** The internal network connection state per each Wi-Fi band. */
81     class NetworkConnectionState {
82         // If the supplicant connection is completed.
83         private boolean mConnected;
84         // If the internet has been validated.
85         private boolean mValidated;
86         // The connection start time in millisecond
87         public long connectionStartTimeMillis;
88         // The WorkSource of the connection requestor.
89         public WorkSource requestorWorkSource;
90         public String bssid;
91 
NetworkConnectionState(WorkSource workSource)92         NetworkConnectionState(WorkSource workSource) {
93             this(workSource, -1L);
94         }
95 
NetworkConnectionState(WorkSource workSource, long connectionStartTime)96         NetworkConnectionState(WorkSource workSource, long connectionStartTime) {
97             requestorWorkSource = workSource;
98             connectionStartTimeMillis = connectionStartTime;
99 
100             mConnected = false;
101             mValidated = false;
102             bssid = null;
103         }
104 
setConnected(boolean connected)105         public NetworkConnectionState setConnected(boolean connected) {
106             mConnected = connected;
107             return this;
108         }
109 
110         @VisibleForTesting
isConnected()111         public boolean isConnected() {
112             return mConnected;
113         }
114 
setValidated(boolean validated)115         public NetworkConnectionState setValidated(boolean validated) {
116             mValidated = validated;
117             return this;
118         }
119 
120         @VisibleForTesting
isValidated()121         public boolean isValidated() {
122             return mValidated;
123         }
124     }
125 
126     @VisibleForTesting
getNetworkConnectionState()127     SparseArray<NetworkConnectionState> getNetworkConnectionState() {
128         return mNetworkConnectionStates;
129     }
130 
131     /** The Multi Internet Connection Status Listener. The registered listener will be notified
132      * for the connection status change and scan needed. */
133     public interface ConnectionStatusListener {
134         /** Called when connection status changed */
onStatusChange( @ultiInternetManager.MultiInternetState int state, WorkSource requestorWs)135         void onStatusChange(
136                 @MultiInternetManager.MultiInternetState int state,
137                 WorkSource requestorWs);
138         /** Called when a scan is needed */
onStartScan(WorkSource requestorWs)139         void onStartScan(WorkSource requestorWs);
140     }
141 
getRequestorWorkSource(int band)142     @Nullable private WorkSource getRequestorWorkSource(int band) {
143         if (!mNetworkConnectionStates.contains(band)) {
144             return null;
145         }
146         return mNetworkConnectionStates.get(band).requestorWorkSource;
147     }
148 
149     private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
150         @Override
onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)151         public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
152             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
153                     || !isStaConcurrencyForMultiInternetEnabled()) {
154                 return;
155             }
156             if (!(activeModeManager instanceof ConcreteClientModeManager)) {
157                 return;
158             }
159             final ConcreteClientModeManager ccm = (ConcreteClientModeManager) activeModeManager;
160             // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
161             if (ccm.getRole() != ROLE_CLIENT_SECONDARY_LONG_LIVED || !ccm.isSecondaryInternet()) {
162                 return;
163             }
164             if (mVerboseLoggingEnabled) {
165                 Log.v(TAG, "Secondary ClientModeManager created for internet, connecting!");
166             }
167             updateNetworkConnectionStates();
168         }
169 
170         @Override
onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)171         public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
172             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
173                     || !isStaConcurrencyForMultiInternetEnabled()) {
174                 return;
175             }
176             if (!(activeModeManager instanceof ConcreteClientModeManager)) {
177                 return;
178             }
179             final ConcreteClientModeManager ccm = (ConcreteClientModeManager) activeModeManager;
180             // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
181             // Needs to call getPreviousRole here since the role is set to null after a CMM stops
182             if (ccm.getPreviousRole() != ROLE_CLIENT_SECONDARY_LONG_LIVED
183                     || !ccm.isSecondaryInternet()) {
184                 return;
185             }
186             if (mVerboseLoggingEnabled) {
187                 Log.v(TAG, "ClientModeManager for internet removed");
188             }
189             // A secondary cmm was removed because of the connection was lost, start scan
190             // to find the new network connection.
191             updateNetworkConnectionStates();
192             if (hasPendingConnectionRequests()) {
193                 startConnectivityScan();
194             }
195         }
196 
197         @Override
onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)198         public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
199             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
200                     || !isStaConcurrencyForMultiInternetEnabled()) {
201                 return;
202             }
203             if (!(activeModeManager instanceof ConcreteClientModeManager)) {
204                 return;
205             }
206             final ConcreteClientModeManager ccm = (ConcreteClientModeManager) activeModeManager;
207             if (ccm.getPreviousRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED
208                     && ccm.isSecondaryInternet()) {
209                 Log.w(TAG, "Secondary client mode manager changed role to "
210                         + ccm.getRole());
211             }
212             updateNetworkConnectionStates();
213         }
214     }
215 
216     private class ClientModeListenerInternal implements ClientModeImplListener {
217         @Override
onInternetValidated(@onNull ConcreteClientModeManager clientModeManager)218         public void onInternetValidated(@NonNull ConcreteClientModeManager clientModeManager) {
219             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
220                     || !isStaConcurrencyForMultiInternetEnabled()) {
221                 return;
222             }
223             final WifiInfo info = clientModeManager.getConnectionInfo();
224             if (info != null) {
225                 final int band = ScanResult.toBand(info.getFrequency());
226                 if (mNetworkConnectionStates.contains(band)) {
227                     mNetworkConnectionStates.get(band).setValidated(true);
228                 }
229             }
230             if (mVerboseLoggingEnabled) {
231                 Log.v(TAG, "ClientModeManager role " + clientModeManager.getRole()
232                         + " internet validated for connection");
233             }
234             // If the primary role was connected and internet validated, update the connection state
235             // immediately and issue scan for secondary network connection if needed.
236             // If the secondary role was connected and internet validated, update the connection
237             // state and notify connectivity manager.
238             // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
239             if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY) {
240                 updateNetworkConnectionStates();
241                 final int band = findUnconnectedRequestBand();
242                 if (band != ScanResult.UNSPECIFIED) {
243                     // Trigger the connectivity scan
244                     mConnectionStatusListener.onStartScan(getRequestorWorkSource(band));
245                 }
246             } else if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED
247                     && clientModeManager.isSecondaryInternet()) {
248                 updateNetworkConnectionStates();
249             }
250         }
251 
252         // TODO(b/175896748): not yet triggered by ClientModeImpl
253         @Override
onL3Connected(@onNull ConcreteClientModeManager clientModeManager)254         public void onL3Connected(@NonNull ConcreteClientModeManager clientModeManager) {
255             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
256                     || !isStaConcurrencyForMultiInternetEnabled()) {
257                 return;
258             }
259             // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
260             if (clientModeManager.getRole() != ROLE_CLIENT_SECONDARY_LONG_LIVED
261                     || !clientModeManager.isSecondaryInternet()) {
262                 return;
263             }
264             updateNetworkConnectionStates();
265             // If no pending connection requests, update connection listener.
266             if (!hasPendingConnectionRequests()) {
267                 final int band = getSecondaryConnectedNetworkBand();
268                 if (band == ScanResult.UNSPECIFIED) return;
269                 final long connectionTime = mClock.getElapsedSinceBootMillis()
270                         - mNetworkConnectionStates.get(band).connectionStartTimeMillis;
271                 if (mVerboseLoggingEnabled) {
272                     Log.v(TAG, "ClientModeManager for internet L3 connected for "
273                             + connectionTime + " ms.");
274                 }
275                 handleConnectionStateChange(MULTI_INTERNET_STATE_CONNECTED,
276                         getRequestorWorkSource(band));
277             }
278         }
279 
280         @Override
onConnectionEnd(@onNull ConcreteClientModeManager clientModeManager)281         public void onConnectionEnd(@NonNull ConcreteClientModeManager clientModeManager) {
282             if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
283                     || !isStaConcurrencyForMultiInternetEnabled()) {
284                 return;
285             }
286             if (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY) {
287                 if (mVerboseLoggingEnabled) {
288                     Log.v(TAG, "Connection end on primary client mode manager");
289                 }
290                 // When the primary network connection is ended, disconnect the secondary network,
291                 // as the secondary network is opportunistic.
292                 // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
293                 for (ConcreteClientModeManager cmm : mActiveModeWarden.getClientModeManagersInRoles(
294                         ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
295                     if (cmm.isSecondaryInternet()) {
296                         if (mVerboseLoggingEnabled) {
297                             Log.v(TAG, "Disconnect secondary client mode manager");
298                         }
299                         cmm.disconnect();
300                     }
301                 }
302                 // As the secondary network is disconnected, mark all bands as disconnected.
303                 for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
304                     mNetworkConnectionStates.valueAt(i).setConnected(false);
305                 }
306             }
307             updateNetworkConnectionStates();
308         }
309     }
310 
MultiInternetManager( @onNull ActiveModeWarden activeModeWarden, @NonNull FrameworkFacade frameworkFacade, @NonNull Context context, @NonNull ClientModeImplMonitor cmiMonitor, @NonNull WifiSettingsStore settingsStore, @NonNull Handler handler, @NonNull Clock clock)311     public MultiInternetManager(
312             @NonNull ActiveModeWarden activeModeWarden,
313             @NonNull FrameworkFacade frameworkFacade,
314             @NonNull Context context,
315             @NonNull ClientModeImplMonitor cmiMonitor,
316             @NonNull WifiSettingsStore settingsStore,
317             @NonNull Handler handler,
318             @NonNull Clock clock) {
319         mActiveModeWarden = activeModeWarden;
320         mFrameworkFacade = frameworkFacade;
321         mContext = context;
322         mCmiMonitor = cmiMonitor;
323         mSettingsStore = settingsStore;
324         mEventHandler = handler;
325         mClock = clock;
326         mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
327         cmiMonitor.registerListener(new ClientModeListenerInternal());
328         mStaConcurrencyMultiInternetMode = mSettingsStore.getWifiMultiInternetMode();
329     }
330 
331     /**
332      * Check if Wi-Fi multi internet use case is enabled.
333      *
334      * @return true if Wi-Fi multi internet use case is enabled.
335      */
isStaConcurrencyForMultiInternetEnabled()336     public boolean isStaConcurrencyForMultiInternetEnabled() {
337         return mStaConcurrencyMultiInternetMode
338             != WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED;
339     }
340 
341     /**
342      * Check if Wi-Fi multi internet use case allows multi AP.
343      *
344      * @return true if Wi-Fi multi internet use case allows multi AP.
345      */
isStaConcurrencyForMultiInternetMultiApAllowed()346     public boolean isStaConcurrencyForMultiInternetMultiApAllowed() {
347         return mStaConcurrencyMultiInternetMode
348                 == WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP;
349     }
350 
351     /**
352      * Return Wi-Fi multi internet use case mode.
353      *
354      * @return Current mode of Wi-Fi multi internet use case.
355      */
getStaConcurrencyForMultiInternetMode()356     public @WifiManager.WifiMultiInternetMode int getStaConcurrencyForMultiInternetMode() {
357         if (mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()) {
358             return mStaConcurrencyMultiInternetMode;
359         }
360         return WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED;
361     }
362 
363     /**
364      * Set the multi internet use case mode.
365      * @return true if the mode set successfully, false if failed.
366      */
setStaConcurrencyForMultiInternetMode( @ifiManager.WifiMultiInternetMode int mode)367     public boolean setStaConcurrencyForMultiInternetMode(
368             @WifiManager.WifiMultiInternetMode int mode) {
369         final boolean enabled = (mode != WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
370         if (!mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()) {
371             return false;
372         }
373 
374         if (mode == mStaConcurrencyMultiInternetMode) {
375             return true;
376         }
377         mStaConcurrencyMultiInternetMode = mode;
378         // If the STA+STA multi internet feature was disabled, disconnect the secondary cmm.
379         // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
380         if (!enabled) {
381             for (ConcreteClientModeManager cmm : mActiveModeWarden.getClientModeManagersInRoles(
382                     ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
383                 if (cmm.isSecondaryInternet()) {
384                     cmm.disconnect();
385                 }
386             }
387             handleConnectionStateChange(MULTI_INTERNET_STATE_NONE, null);
388         } else {
389             updateNetworkConnectionStates();
390             final int band = findUnconnectedRequestBand();
391             if (band != ScanResult.UNSPECIFIED) {
392                 handleConnectionStateChange(MULTI_INTERNET_STATE_CONNECTION_REQUESTED,
393                         getRequestorWorkSource(band));
394             }
395         }
396 
397         mSettingsStore.handleWifiMultiInternetMode(mode);
398         // Check if there is already multi internet request then start scan for connection.
399         if (hasPendingConnectionRequests()) {
400             startConnectivityScan();
401         }
402         return true;
403     }
404 
setVerboseLoggingEnabled(boolean enabled)405     public void setVerboseLoggingEnabled(boolean enabled) {
406         mVerboseLoggingEnabled = enabled;
407     }
408 
409     /** Set the Multi Internet Connection Status listener.
410      *
411      * @param listener The Multi Internet Connection Status listener.
412      */
setConnectionStatusListener(ConnectionStatusListener listener)413     public void setConnectionStatusListener(ConnectionStatusListener listener) {
414         mConnectionStatusListener = listener;
415     }
416 
417     /** Notify the BSSID associated event from ClientModeImpl. Triggered by
418      *  WifiMonitor.ASSOCIATED_BSSID_EVENT.
419      *  @param clientModeManager the client mode manager with BSSID associated event.
420      */
notifyBssidAssociatedEvent(ConcreteClientModeManager clientModeManager)421     public void notifyBssidAssociatedEvent(ConcreteClientModeManager clientModeManager) {
422         if (clientModeManager.getRole() != ROLE_CLIENT_PRIMARY
423                 || !mActiveModeWarden.isStaStaConcurrencySupportedForMultiInternet()
424                 || !isStaConcurrencyForMultiInternetEnabled()) {
425             return;
426         }
427         // If primary CMM has associated to a new BSSID, need to check if it is in a different band
428         // of secondary CMM.
429         final WifiInfo info = clientModeManager.getConnectionInfo();
430         final ConcreteClientModeManager secondaryCcmm =
431                 mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_LONG_LIVED);
432         // If no secondary client mode manager then it's ok
433         if (secondaryCcmm == null) return;
434         // If secondary client mode manager is not connected or not for secondary internet
435         if (!secondaryCcmm.isConnected() || !secondaryCcmm.isSecondaryInternet()) return;
436         final WifiInfo info2 = secondaryCcmm.getConnectionInfo();
437         // If secondary network is in same band as primary now
438         if (ScanResult.toBand(info.getFrequency()) == ScanResult.toBand(info2.getFrequency())) {
439             // Need to disconnect secondary network
440             secondaryCcmm.disconnect();
441             // As the secondary network is disconnected, mark all bands as disconnected.
442             for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
443                 mNetworkConnectionStates.valueAt(i).setConnected(false);
444             }
445             updateNetworkConnectionStates();
446         }
447     }
448 
449     /** Check if there is a connection request for multi internet
450      * @return true if there is one or more connection request
451      */
hasPendingConnectionRequests()452     public boolean hasPendingConnectionRequests() {
453         return findUnconnectedRequestBand() != ScanResult.UNSPECIFIED;
454     }
455 
456     /**
457      * Check if there is unconnected network connection request.
458      * @return the band of the connection request that is still not connected.
459      */
findUnconnectedRequestBand()460     public int findUnconnectedRequestBand() {
461         if (mStaConcurrencyMultiInternetMode == WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED) {
462             return ScanResult.UNSPECIFIED;
463         }
464         for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
465             if (!mNetworkConnectionStates.valueAt(i).isConnected()) {
466                 return mNetworkConnectionStates.keyAt(i);
467             }
468         }
469         return ScanResult.UNSPECIFIED;
470     }
471 
472     /**
473      * Return the mapping from band (one of ScanResult.WIFI_BAND_*) to the specified BSSID for that
474      * band.
475      */
getSpecifiedBssids()476     public Map<Integer, String> getSpecifiedBssids() {
477         Map<Integer, String> result = new ArrayMap<>();
478         for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
479             int band = mNetworkConnectionStates.keyAt(i);
480             String bssid = mNetworkConnectionStates.valueAt(i).bssid;
481             if (bssid != null) {
482                 result.put(band, bssid);
483             }
484         }
485         return result;
486     }
487 
488     /**
489      * Traverse the client mode managers and update the internal connection states.
490      */
updateNetworkConnectionStates()491     private void updateNetworkConnectionStates() {
492         for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
493             mNetworkConnectionStates.valueAt(i).setConnected(false);
494         }
495 
496         for (ClientModeManager clientModeManager :
497                 mActiveModeWarden.getInternetConnectivityClientModeManagers()) {
498             // TODO: b/197670907 : Add client role ROLE_CLIENT_SECONDARY_INTERNET
499             if (clientModeManager instanceof ConcreteClientModeManager
500                     && (clientModeManager.getRole() == ROLE_CLIENT_PRIMARY
501                     || clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
502                 ConcreteClientModeManager ccmm = (ConcreteClientModeManager) clientModeManager;
503                 // Exclude the secondary client mode manager not for secondary internet.
504                 if (ccmm.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED
505                         && !ccmm.isSecondaryInternet()) {
506                     continue;
507                 }
508                 WifiInfo info = clientModeManager.getConnectionInfo();
509                 // Exclude the network that is not connected or restricted.
510                 if (info == null || !clientModeManager.isConnected()
511                         ||  info.isRestricted()) continue;
512                 // Exclude the network that is oem paid/private.
513                 if (SdkLevel.isAtLeastT() && (info.isOemPaid() || info.isOemPrivate())) continue;
514                 final int band = ScanResult.toBand(info.getFrequency());
515                 if (mNetworkConnectionStates.contains(band)) {
516                     // Update the connected state
517                     mNetworkConnectionStates.get(band).setConnected(true);
518                 }
519                 if (mVerboseLoggingEnabled) {
520                     Log.v(TAG, "network band " + band + " role "
521                             + clientModeManager.getRole().toString());
522                 }
523             }
524         }
525         // Handle the state change and notify listener
526         if (!hasPendingConnectionRequests()) {
527             if (mNetworkConnectionStates.size() == 0) {
528                 handleConnectionStateChange(MULTI_INTERNET_STATE_NONE, null);
529             } else {
530                 final int band = getSecondaryConnectedNetworkBand();
531                 if (band == ScanResult.UNSPECIFIED) return;
532                 handleConnectionStateChange(MULTI_INTERNET_STATE_CONNECTED,
533                         getRequestorWorkSource(band));
534             }
535         } else {
536             final int band = findUnconnectedRequestBand();
537             handleConnectionStateChange(MULTI_INTERNET_STATE_CONNECTION_REQUESTED,
538                     getRequestorWorkSource(band));
539         }
540     }
541 
542     /**
543      * Set a network connection request from a requestor WorkSource for a specific band, or clear
544      * the connection request if the WorkSource is null.
545      * Triggered when {@link MultiInternetWifiNetworkFactory} has a pending network request.
546      * @param band The band of the Wi-Fi network requested.
547      * @param requestorWs The requestor's WorkSource. Null to clear a network request for a
548      * a band.
549      */
setMultiInternetConnectionWorksource(int band, String targetBssid, WorkSource requestorWs)550     public void setMultiInternetConnectionWorksource(int band, String targetBssid,
551             WorkSource requestorWs) {
552         if (!isStaConcurrencyForMultiInternetEnabled()) {
553             Log.w(TAG, "MultInternet is not enabled.");
554             return;
555         }
556         if (mVerboseLoggingEnabled) {
557             Log.v(TAG, "setMultiInternetConnectionWorksource: band=" + band + ", bssid="
558                     + targetBssid + ", requestorWs=" + requestorWs);
559         }
560         if (requestorWs == null) {
561             // Disconnect secondary network if the request is removed.
562             if (band == getSecondaryConnectedNetworkBand()) {
563                 for (ConcreteClientModeManager cmm : mActiveModeWarden.getClientModeManagersInRoles(
564                         ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
565                     if (cmm.isSecondaryInternet()) {
566                         cmm.disconnect();
567                     }
568                 }
569             }
570             mNetworkConnectionStates.remove(band);
571             updateNetworkConnectionStates();
572             return;
573         }
574         if (mNetworkConnectionStates.contains(band)) {
575             Log.w(TAG, "band " + band + " already requested.");
576             if (TextUtils.equals(mNetworkConnectionStates.get(band).bssid, targetBssid)) {
577                 // No change in BSSID field, so early return to avoid unnecessary scanning.
578                 return;
579             }
580             disconnectSecondaryIfNeeded(band, targetBssid);
581         }
582         NetworkConnectionState connectionState = new NetworkConnectionState(requestorWs,
583                 mClock.getElapsedSinceBootMillis());
584         connectionState.bssid = targetBssid;
585         mNetworkConnectionStates.put(band, connectionState);
586         startConnectivityScan();
587     }
588 
589     /**
590      * Disconnect the secondary that's connected to the same band but different bssid.
591      */
disconnectSecondaryIfNeeded(int band, String targetBssid)592     private void disconnectSecondaryIfNeeded(int band, String targetBssid) {
593         if (targetBssid == null) {
594             return;
595         }
596         for (ConcreteClientModeManager cmm : mActiveModeWarden.getClientModeManagersInRoles(
597                 ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
598             if (cmm.isSecondaryInternet()) {
599                 WifiInfo wifiInfo = cmm.getConnectionInfo();
600                 if (band != ScanResult.toBand(wifiInfo.getFrequency())) {
601                     continue;
602                 }
603                 String connectingBssid = cmm.getConnectingBssid();
604                 String connectedBssid = cmm.getConnectedBssid();
605                 if ((connectingBssid != null && !connectingBssid.equals(targetBssid))
606                         || (connectedBssid != null && !connectedBssid.equals(targetBssid))) {
607                     if (mVerboseLoggingEnabled) {
608                         Log.v(TAG, "Disconnect secondary client mode manager due to specified"
609                                 + " BSSID in the same band");
610                     }
611                     cmm.disconnect();
612                     break;
613                 }
614             }
615         }
616     }
617 
618     /** Returns the band of the secondary network connected. */
getSecondaryConnectedNetworkBand()619     private int getSecondaryConnectedNetworkBand() {
620         final ConcreteClientModeManager secondaryCcmm =
621                 mActiveModeWarden.getClientModeManagerInRole(ROLE_CLIENT_SECONDARY_LONG_LIVED);
622         if (secondaryCcmm == null) {
623             return ScanResult.UNSPECIFIED;
624         }
625         final WifiInfo info = secondaryCcmm.getConnectionInfo();
626         // Make sure secondary network is connected.
627         if (info == null || !secondaryCcmm.isConnected() || !secondaryCcmm.isSecondaryInternet()) {
628             return ScanResult.UNSPECIFIED;
629         }
630         return ScanResult.toBand(info.getFrequency());
631     }
632 
633     /**
634      * Handles the connection state change and notifies the status listener.
635      * The listener will only be notified when the state changes. If the state remains the same
636      * but with a different requestor WorkSource then the listener is not notified.
637      *
638      * @param state
639      * @param workSource
640      */
handleConnectionStateChange(int state, WorkSource workSource)641     private void handleConnectionStateChange(int state, WorkSource workSource) {
642         if (mMultiInternetConnectionState == state) {
643             return;
644         }
645         mMultiInternetConnectionState = state;
646         mConnectionStatusListener.onStatusChange(state, workSource);
647     }
648 
649     /**
650      * Start a connectivity scan to trigger the network selection process and connect to
651      * the requested multi internet networks.
652      */
startConnectivityScan()653     private void startConnectivityScan() {
654         if (!isStaConcurrencyForMultiInternetEnabled()) {
655             return;
656         }
657         updateNetworkConnectionStates();
658 
659         final int band = findUnconnectedRequestBand();
660         if (band == ScanResult.UNSPECIFIED) return;
661         NetworkConnectionState state = mNetworkConnectionStates.get(band);
662         if (mVerboseLoggingEnabled) {
663             Log.v(TAG, "Schedule connectivity scan for network request with band " + band
664                     + " start time " + state.connectionStartTimeMillis + " now "
665                     + mClock.getElapsedSinceBootMillis());
666         }
667         // Trigger the connectivity scan
668         mConnectionStatusListener.onStartScan(getRequestorWorkSource(band));
669     }
670 
671     /** Dump the internal states of MultiInternetManager */
dump(FileDescriptor fd, PrintWriter pw, String[] args)672     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
673         pw.println("Dump of MultiInternetManager");
674         pw.println(TAG + ": mStaConcurrencyMultiInternetMode "
675                 + mStaConcurrencyMultiInternetMode);
676         for (int i = 0; i < mNetworkConnectionStates.size(); i++) {
677             pw.println("band " + mNetworkConnectionStates.keyAt(i) + " bssid "
678                     + mNetworkConnectionStates.valueAt(i).bssid + " connected "
679                     + mNetworkConnectionStates.valueAt(i).isConnected()
680                     + " validated " + mNetworkConnectionStates.valueAt(i).isValidated());
681         }
682     }
683 
684 }
685